diff --git a/bluetooth/audio/aidl/default/BluetoothAudioProviderFactory.cpp b/bluetooth/audio/aidl/default/BluetoothAudioProviderFactory.cpp index dc36ac0c1a..2a2cab0384 100644 --- a/bluetooth/audio/aidl/default/BluetoothAudioProviderFactory.cpp +++ b/bluetooth/audio/aidl/default/BluetoothAudioProviderFactory.cpp @@ -184,12 +184,13 @@ ndk::ScopedAStatus BluetoothAudioProviderFactory::getProviderInfo( SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH) { std::vector db_codec_info = BluetoothAudioCodecs::GetLeAudioOffloadCodecInfo(session_type); - if (!db_codec_info.empty()) { - auto& provider_info = _aidl_return->emplace(); - provider_info.name = kLeAudioOffloadProviderName; - provider_info.codecInfos = db_codec_info; - return ndk::ScopedAStatus::ok(); - } + // Return provider info supports without checking db_codec_info + // This help with various flow implementation for multidirectional support. + auto& provider_info = _aidl_return->emplace(); + provider_info.supportsMultidirectionalCapabilities = true; + provider_info.name = kLeAudioOffloadProviderName; + provider_info.codecInfos = db_codec_info; + return ndk::ScopedAStatus::ok(); } if (session_type == SessionType::HFP_HARDWARE_OFFLOAD_DATAPATH) { diff --git a/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.cpp b/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.cpp index a692d84f99..5002a1aeda 100644 --- a/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.cpp +++ b/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.cpp @@ -165,8 +165,8 @@ bool LeAudioOffloadAudioProvider::isMatchedValidCodec(CodecId cfg_codec, return cfg_codec == req_codec; } -bool LeAudioOffloadAudioProvider::isCapabilitiesMatchedContext( - AudioContext setting_context, +bool LeAudioOffloadAudioProvider::filterCapabilitiesMatchedContext( + AudioContext& setting_context, const IBluetoothAudioProvider::LeAudioDeviceCapabilities& capabilities) { // If has no metadata, assume match if (!capabilities.metadata.has_value()) return true; @@ -178,7 +178,11 @@ bool LeAudioOffloadAudioProvider::isCapabilitiesMatchedContext( auto& context = metadata.value() .get() .values; - if (setting_context.bitmask & context.bitmask) return true; + if (setting_context.bitmask & context.bitmask) { + // New mask with matched capability + setting_context.bitmask &= context.bitmask; + return true; + } } } @@ -189,8 +193,12 @@ bool LeAudioOffloadAudioProvider::isMatchedSamplingFreq( CodecSpecificConfigurationLtv::SamplingFrequency& cfg_freq, CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies& capability_freq) { - for (auto [freq, bitmask] : freq_to_support_bitmask_map) - if (cfg_freq == freq) return (capability_freq.bitmask & bitmask); + auto p = freq_to_support_bitmask_map.find(cfg_freq); + if (p != freq_to_support_bitmask_map.end()) { + if (capability_freq.bitmask & p->second) { + return true; + } + } return false; } @@ -198,9 +206,11 @@ bool LeAudioOffloadAudioProvider::isMatchedFrameDuration( CodecSpecificConfigurationLtv::FrameDuration& cfg_fduration, CodecSpecificCapabilitiesLtv::SupportedFrameDurations& capability_fduration) { - for (auto [fduration, bitmask] : fduration_to_support_fduration_map) - if (cfg_fduration == fduration) - return (capability_fduration.bitmask & bitmask); + auto p = fduration_to_support_fduration_map.find(cfg_fduration); + if (p != fduration_to_support_fduration_map.end()) + if (capability_fduration.bitmask & p->second) { + return true; + } return false; } @@ -240,50 +250,67 @@ bool LeAudioOffloadAudioProvider::isCapabilitiesMatchedCodecConfiguration( for (auto& codec_capability : codec_capabilities) { auto cfg = cfg_tag_map.find(cap_to_cfg_tag_map[codec_capability.getTag()]); - // Cannot find tag for the capability: - if (cfg == cfg_tag_map.end()) return false; + // If capability has this tag, but our configuration doesn't + // Then we will assume it is matched + if (cfg == cfg_tag_map.end()) { + continue; + } - // Matching logic for sampling frequency - if (codec_capability.getTag() == - CodecSpecificCapabilitiesLtv::Tag::supportedSamplingFrequencies) { - if (!isMatchedSamplingFreq( - cfg->second - .get(), - codec_capability.get())) - return false; - } else if (codec_capability.getTag() == - CodecSpecificCapabilitiesLtv::Tag::supportedFrameDurations) { - if (!isMatchedFrameDuration( - cfg->second - .get(), - codec_capability.get())) - return false; - } else if (codec_capability.getTag() == - CodecSpecificCapabilitiesLtv::Tag::supportedAudioChannelCounts) { - if (!isMatchedAudioChannel( - cfg->second.get< - CodecSpecificConfigurationLtv::Tag::audioChannelAllocation>(), - codec_capability.get())) - return false; - } else if (codec_capability.getTag() == CodecSpecificCapabilitiesLtv::Tag:: - supportedMaxCodecFramesPerSDU) { - if (!isMatchedCodecFramesPerSDU( - cfg->second.get< - CodecSpecificConfigurationLtv::Tag::codecFrameBlocksPerSDU>(), - codec_capability.get())) - return false; - } else if (codec_capability.getTag() == CodecSpecificCapabilitiesLtv::Tag:: - supportedOctetsPerCodecFrame) { - if (!isMatchedOctetsPerCodecFrame( - cfg->second.get< - CodecSpecificConfigurationLtv::Tag::octetsPerCodecFrame>(), - codec_capability.get())) - return false; + switch (codec_capability.getTag()) { + case CodecSpecificCapabilitiesLtv::Tag::supportedSamplingFrequencies: { + if (!isMatchedSamplingFreq( + cfg->second.get< + CodecSpecificConfigurationLtv::Tag::samplingFrequency>(), + codec_capability.get())) { + return false; + } + break; + } + + case CodecSpecificCapabilitiesLtv::Tag::supportedFrameDurations: { + if (!isMatchedFrameDuration( + cfg->second + .get(), + codec_capability.get())) { + return false; + } + break; + } + + case CodecSpecificCapabilitiesLtv::Tag::supportedAudioChannelCounts: { + if (!isMatchedAudioChannel( + cfg->second.get(), + codec_capability.get())) { + return false; + } + break; + } + + case CodecSpecificCapabilitiesLtv::Tag::supportedMaxCodecFramesPerSDU: { + if (!isMatchedCodecFramesPerSDU( + cfg->second.get(), + codec_capability.get())) { + return false; + } + break; + } + + case CodecSpecificCapabilitiesLtv::Tag::supportedOctetsPerCodecFrame: { + if (!isMatchedOctetsPerCodecFrame( + cfg->second.get< + CodecSpecificConfigurationLtv::Tag::octetsPerCodecFrame>(), + codec_capability.get())) { + return false; + } + break; + } } } @@ -298,11 +325,16 @@ bool LeAudioOffloadAudioProvider::isMatchedAseConfiguration( if (requirement_cfg.codecId.has_value()) { if (!setting_cfg.codecId.has_value()) return false; if (!isMatchedValidCodec(setting_cfg.codecId.value(), - requirement_cfg.codecId.value())) + requirement_cfg.codecId.value())) { return false; + } } - if (setting_cfg.targetLatency != requirement_cfg.targetLatency) return false; + if (requirement_cfg.targetLatency == + LeAudioAseConfiguration::TargetLatency::UNDEFINED || + setting_cfg.targetLatency != requirement_cfg.targetLatency) { + return false; + } // Ignore PHY requirement // Check all codec configuration @@ -314,9 +346,13 @@ bool LeAudioOffloadAudioProvider::isMatchedAseConfiguration( for (auto requirement_cfg : requirement_cfg.codecConfiguration) { // Directly compare CodecSpecificConfigurationLtv auto cfg = cfg_tag_map.find(requirement_cfg.getTag()); - if (cfg == cfg_tag_map.end()) return false; + if (cfg == cfg_tag_map.end()) { + return false; + } - if (cfg->second != requirement_cfg) return false; + if (cfg->second != requirement_cfg) { + return false; + } } // Ignore vendor configuration and metadata requirement @@ -326,10 +362,13 @@ bool LeAudioOffloadAudioProvider::isMatchedAseConfiguration( bool LeAudioOffloadAudioProvider::isMatchedBISConfiguration( LeAudioBisConfiguration bis_cfg, const IBluetoothAudioProvider::LeAudioDeviceCapabilities& capabilities) { - if (!isMatchedValidCodec(bis_cfg.codecId, capabilities.codecId)) return false; - if (!isCapabilitiesMatchedCodecConfiguration( - bis_cfg.codecConfiguration, capabilities.codecSpecificCapabilities)) + if (!isMatchedValidCodec(bis_cfg.codecId, capabilities.codecId)) { return false; + } + if (!isCapabilitiesMatchedCodecConfiguration( + bis_cfg.codecConfiguration, capabilities.codecSpecificCapabilities)) { + return false; + } return true; } @@ -357,31 +396,35 @@ void LeAudioOffloadAudioProvider::filterCapabilitiesAseDirectionConfiguration( } void LeAudioOffloadAudioProvider::filterRequirementAseDirectionConfiguration( - std::vector>& + std::optional>>& direction_configurations, - const std::optional>>& - requirements, - std::vector>& + const std::vector>& requirements, + std::optional>>& valid_direction_configurations) { - for (auto direction_configuration : direction_configurations) { - if (!requirements.has_value()) { - // If there's no requirement, all are valid - valid_direction_configurations.push_back(direction_configuration); - continue; - } - if (!direction_configuration.has_value()) continue; - - for (auto& requirement : requirements.value()) { - if (!requirement.has_value()) continue; + 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; + for (auto direction_configuration : direction_configurations.value()) { + if (!direction_configuration.has_value()) continue; if (!isMatchedAseConfiguration( direction_configuration.value().aseConfiguration, requirement.value().aseConfiguration)) continue; // Valid if match any requirement. - valid_direction_configurations.push_back(direction_configuration); + valid_direction_configurations.value().push_back(direction_configuration); break; } } + // 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; + } } /* Get a new LeAudioAseConfigurationSetting by matching a setting with a @@ -392,8 +435,18 @@ LeAudioOffloadAudioProvider::getCapabilitiesMatchedAseConfigurationSettings( IBluetoothAudioProvider::LeAudioAseConfigurationSetting& setting, const IBluetoothAudioProvider::LeAudioDeviceCapabilities& capabilities, uint8_t direction) { + // Create a new LeAudioAseConfigurationSetting and return + // For other direction will contain all settings + LeAudioAseConfigurationSetting filtered_setting{ + .audioContext = setting.audioContext, + .sinkAseConfiguration = setting.sinkAseConfiguration, + .sourceAseConfiguration = setting.sourceAseConfiguration, + .flags = setting.flags, + .packing = setting.packing, + }; // Try to match context in metadata. - if (!isCapabilitiesMatchedContext(setting.audioContext, capabilities)) + if (!filterCapabilitiesMatchedContext(filtered_setting.audioContext, + capabilities)) return std::nullopt; // Get a list of all matched AseDirectionConfiguration @@ -401,28 +454,30 @@ LeAudioOffloadAudioProvider::getCapabilitiesMatchedAseConfigurationSettings( std::vector>* direction_configuration = nullptr; if (direction == kLeAudioDirectionSink) { - if (!setting.sinkAseConfiguration.has_value()) return std::nullopt; - direction_configuration = &setting.sinkAseConfiguration.value(); + if (!filtered_setting.sinkAseConfiguration.has_value()) return std::nullopt; + direction_configuration = &filtered_setting.sinkAseConfiguration.value(); } else { - if (!setting.sourceAseConfiguration.has_value()) return std::nullopt; - direction_configuration = &setting.sourceAseConfiguration.value(); + if (!filtered_setting.sourceAseConfiguration.has_value()) + return std::nullopt; + direction_configuration = &filtered_setting.sourceAseConfiguration.value(); } std::vector> valid_direction_configuration; filterCapabilitiesAseDirectionConfiguration( *direction_configuration, capabilities, valid_direction_configuration); - if (valid_direction_configuration.empty()) return std::nullopt; + + // No valid configuration for this direction + if (valid_direction_configuration.empty()) { + return std::nullopt; + } // Create a new LeAudioAseConfigurationSetting and return - LeAudioAseConfigurationSetting filtered_setting; - filtered_setting.audioContext = setting.audioContext; - filtered_setting.packing = setting.packing; + // For other direction will contain all settings if (direction == kLeAudioDirectionSink) { filtered_setting.sinkAseConfiguration = valid_direction_configuration; } else { filtered_setting.sourceAseConfiguration = valid_direction_configuration; } - filtered_setting.flags = setting.flags; return filtered_setting; } @@ -436,41 +491,49 @@ LeAudioOffloadAudioProvider::getRequirementMatchedAseConfigurationSettings( const IBluetoothAudioProvider::LeAudioConfigurationRequirement& requirement) { // Try to match context in metadata. - if (setting.audioContext != requirement.audioContext) return std::nullopt; + if ((setting.audioContext.bitmask & requirement.audioContext.bitmask) != + requirement.audioContext.bitmask) + return std::nullopt; + + // 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; - if (setting.sinkAseConfiguration.has_value()) { - direction_configuration = &setting.sinkAseConfiguration.value(); - direction_requirement = &requirement.sinkAseRequirement; - } else { - direction_configuration = &setting.sourceAseConfiguration.value(); - direction_requirement = &requirement.sourceAseRequirement; + + // Create a new LeAudioAseConfigurationSetting to return + LeAudioAseConfigurationSetting filtered_setting{ + .audioContext = setting.audioContext, + .packing = setting.packing, + .flags = setting.flags, + }; + + if (requirement.sinkAseRequirement.has_value()) { + filterRequirementAseDirectionConfiguration( + setting.sinkAseConfiguration, requirement.sinkAseRequirement.value(), + filtered_setting.sinkAseConfiguration); + if (!filtered_setting.sinkAseConfiguration.has_value()) return std::nullopt; } - std::vector> - valid_direction_configuration; - filterRequirementAseDirectionConfiguration(*direction_configuration, - *direction_requirement, - valid_direction_configuration); - if (valid_direction_configuration.empty()) return std::nullopt; - - // Create a new LeAudioAseConfigurationSetting and return - LeAudioAseConfigurationSetting filtered_setting; - filtered_setting.audioContext = setting.audioContext; - filtered_setting.packing = setting.packing; - if (setting.sinkAseConfiguration.has_value()) - filtered_setting.sinkAseConfiguration = valid_direction_configuration; - else - filtered_setting.sourceAseConfiguration = valid_direction_configuration; - filtered_setting.flags = setting.flags; + if (requirement.sourceAseRequirement.has_value()) { + filterRequirementAseDirectionConfiguration( + setting.sourceAseConfiguration, + requirement.sourceAseRequirement.value(), + filtered_setting.sourceAseConfiguration); + if (!filtered_setting.sourceAseConfiguration.has_value()) + return std::nullopt; + } return filtered_setting; } +// For each requirement, a valid ASE configuration will satify: +// - matched with any sink capability (if presented) +// - OR matched with any source capability (if presented) +// - and the setting need to pass the requirement ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseConfiguration( const std::optional>>& @@ -487,46 +550,81 @@ ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseConfiguration( ase_configuration_settings = BluetoothAudioCodecs::GetLeAudioAseConfigurationSettings(); - // Currently won't handle case where both sink and source capabilities - // are passed in. Only handle one of them. - const std::optional>>* - in_remoteAudioCapabilities; - uint8_t direction = 0; - if (in_remoteSinkAudioCapabilities.has_value()) { - direction = kLeAudioDirectionSink; - in_remoteAudioCapabilities = &in_remoteSinkAudioCapabilities; - } else { - direction = kLeAudioDirectionSource; - in_remoteAudioCapabilities = &in_remoteSourceAudioCapabilities; + if (!in_remoteSinkAudioCapabilities.has_value() && + !in_remoteSourceAudioCapabilities.has_value()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } + // Each setting consist of source and sink AseDirectionConfiguration vector + // Filter every sink capability std::vector - capability_matched_ase_configuration_settings; - // Matching with remote capabilities - for (auto& setting : ase_configuration_settings) { - for (auto& capability : in_remoteAudioCapabilities->value()) { - if (!capability.has_value()) continue; - auto filtered_ase_configuration_setting = - getCapabilitiesMatchedAseConfigurationSettings( - setting, capability.value(), direction); - if (filtered_ase_configuration_setting.has_value()) { - capability_matched_ase_configuration_settings.push_back( - filtered_ase_configuration_setting.value()); + matched_ase_configuration_settings; + + if (in_remoteSinkAudioCapabilities.has_value()) { + // Matching each setting with any remote capabilities + 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( + filtered_ase_configuration_setting.value()); + } } } } - // Matching with requirements + // Combine filter every source capability + if (in_remoteSourceAudioCapabilities.has_value()) { + // Matching each setting with any remote capabilities + for (auto& setting : ase_configuration_settings) { + for (auto& capability : in_remoteSourceAudioCapabilities.value()) { + if (!capability.has_value()) continue; + auto filtered_ase_configuration_setting = + getCapabilitiesMatchedAseConfigurationSettings( + setting, capability.value(), kLeAudioDirectionSource); + if (filtered_ase_configuration_setting.has_value()) { + // Put into the same list + // possibly duplicated, filtered by requirement later + 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& setting : capability_matched_ase_configuration_settings) { - for (auto& requirement : in_requirements) { + 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); 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; + } } *_aidl_return = result; @@ -537,41 +635,45 @@ bool LeAudioOffloadAudioProvider::isMatchedQosRequirement( LeAudioAseQosConfiguration setting_qos, AseQosDirectionRequirement requirement_qos) { if (setting_qos.retransmissionNum != - requirement_qos.preferredRetransmissionNum) + requirement_qos.preferredRetransmissionNum) { return false; - if (setting_qos.maxTransportLatencyMs > requirement_qos.maxTransportLatencyMs) + } + if (setting_qos.maxTransportLatencyMs > + requirement_qos.maxTransportLatencyMs) { return false; + } // Ignore other parameters, as they are not populated in the setting_qos return true; } -ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseQosConfiguration( - const IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement& - in_qosRequirement, - IBluetoothAudioProvider::LeAudioAseQosConfigurationPair* _aidl_return) { - IBluetoothAudioProvider::LeAudioAseQosConfigurationPair result; - // Get all configuration settings - std::vector - ase_configuration_settings = - BluetoothAudioCodecs::GetLeAudioAseConfigurationSettings(); +bool isValidQosRequirement(AseQosDirectionRequirement qosRequirement) { + return ((qosRequirement.maxTransportLatencyMs > 0) && + (qosRequirement.presentationDelayMaxUs > 0) && + (qosRequirement.presentationDelayMaxUs >= + qosRequirement.presentationDelayMinUs)); +} - // Direction QoS matching - // Only handle one direction input case - uint8_t direction = 0; +std::optional +LeAudioOffloadAudioProvider::getDirectionQosConfiguration( + uint8_t direction, + const IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement& + qosRequirement, + std::vector& ase_configuration_settings) { std::optional direction_qos_requirement = std::nullopt; - if (in_qosRequirement.sinkAseQosRequirement.has_value()) { - direction_qos_requirement = in_qosRequirement.sinkAseQosRequirement.value(); - direction = kLeAudioDirectionSink; - } else if (in_qosRequirement.sourceAseQosRequirement.has_value()) { - direction_qos_requirement = - in_qosRequirement.sourceAseQosRequirement.value(); - direction = kLeAudioDirectionSource; + + // Get the correct direction + if (direction == kLeAudioDirectionSink) { + direction_qos_requirement = qosRequirement.sinkAseQosRequirement.value(); + } else { + direction_qos_requirement = qosRequirement.sourceAseQosRequirement.value(); } for (auto& setting : ase_configuration_settings) { // Context matching - if (setting.audioContext != in_qosRequirement.audioContext) continue; + if ((setting.audioContext.bitmask & qosRequirement.audioContext.bitmask) != + qosRequirement.audioContext.bitmask) + continue; // Match configuration flags // Currently configuration flags are not populated, ignore. @@ -592,10 +694,7 @@ ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseQosConfiguration( if (!cfg.has_value()) continue; // If no requirement, return the first QoS if (!direction_qos_requirement.has_value()) { - result.sinkQosConfiguration = cfg.value().qosConfiguration; - result.sourceQosConfiguration = cfg.value().qosConfiguration; - *_aidl_return = result; - return ndk::ScopedAStatus::ok(); + return cfg.value().qosConfiguration; } // If has requirement, return the first matched QoS @@ -607,17 +706,41 @@ ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseQosConfiguration( direction_qos_requirement.value().aseConfiguration) && isMatchedQosRequirement(cfg.value().qosConfiguration.value(), direction_qos_requirement.value())) { - if (direction == kLeAudioDirectionSink) - result.sinkQosConfiguration = cfg.value().qosConfiguration; - else - result.sourceQosConfiguration = cfg.value().qosConfiguration; - *_aidl_return = result; - return ndk::ScopedAStatus::ok(); + return cfg.value().qosConfiguration; } } } - // No match, return empty QoS + return std::nullopt; +} + +ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseQosConfiguration( + const IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement& + in_qosRequirement, + IBluetoothAudioProvider::LeAudioAseQosConfigurationPair* _aidl_return) { + IBluetoothAudioProvider::LeAudioAseQosConfigurationPair result; + + // Get all configuration settings + std::vector + ase_configuration_settings = + BluetoothAudioCodecs::GetLeAudioAseConfigurationSettings(); + + // Direction QoS matching + // Only handle one direction input case + 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); + } + 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); + } + *_aidl_return = result; return ndk::ScopedAStatus::ok(); }; @@ -649,9 +772,13 @@ void LeAudioOffloadAudioProvider::getBroadcastSettings() { BluetoothAudioCodecs::GetLeAudioOffloadCodecInfo( SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH); broadcast_settings.clear(); + + // Default value for unmapped fields CodecSpecificConfigurationLtv::AudioChannelAllocation default_allocation; default_allocation.bitmask = CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_CENTER; + CodecSpecificConfigurationLtv::CodecFrameBlocksPerSDU default_frame; + default_frame.value = 2; for (auto& codec_info : db_codec_info) { if (codec_info.transport.getTag() != CodecInfo::Transport::leAudio) @@ -669,15 +796,20 @@ void LeAudioOffloadAudioProvider::getBroadcastSettings() { octets.value = transport.bitdepth[0]; bis_cfg.codecConfiguration = { - sampling_freq_map[transport.samplingFrequencyHz[0]], octets, - frame_duration_map[transport.frameDurationUs[0]], default_allocation}; + sampling_freq_map[transport.samplingFrequencyHz[0]], + octets, + frame_duration_map[transport.frameDurationUs[0]], + default_allocation, + default_frame, + }; // Add information to structure IBluetoothAudioProvider::LeAudioSubgroupBisConfiguration sub_bis_cfg; - sub_bis_cfg.numBis = 1; + sub_bis_cfg.numBis = 2; sub_bis_cfg.bisConfiguration = bis_cfg; IBluetoothAudioProvider::LeAudioBroadcastSubgroupConfiguration sub_cfg; - sub_cfg.bisConfigurations = {sub_bis_cfg}; + // Populate the same sub config + sub_cfg.bisConfigurations = {sub_bis_cfg, sub_bis_cfg}; setting.subgroupsConfigurations = {sub_cfg}; broadcast_settings.push_back(setting); @@ -721,6 +853,36 @@ LeAudioOffloadAudioProvider:: return filtered_setting; } +bool LeAudioOffloadAudioProvider::isSubgroupConfigurationMatchedContext( + AudioContext setting_context, + 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; + } + + // 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); +} + ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioBroadcastConfiguration( const std::optional filtered_settings; if (!in_remoteSinkAudioCapabilities.has_value()) { - LOG(WARNING) << __func__ << ": Empty capability"; - return ndk::ScopedAStatus::ok(); - } - for (auto& setting : broadcast_settings) { - for (auto& capability : in_remoteSinkAudioCapabilities.value()) { - if (!capability.has_value()) continue; - auto filtered_setting = - getCapabilitiesMatchedBroadcastConfigurationSettings( - setting, capability.value()); - if (filtered_setting.has_value()) - filtered_settings.push_back(filtered_setting.value()); + LOG(INFO) << __func__ << ": Empty capability, get all broadcast settings"; + filtered_settings = broadcast_settings; + } else { + for (auto& setting : broadcast_settings) { + for (auto& capability : in_remoteSinkAudioCapabilities.value()) { + if (!capability.has_value()) continue; + auto filtered_setting = + getCapabilitiesMatchedBroadcastConfigurationSettings( + setting, capability.value()); + if (filtered_setting.has_value()) + filtered_settings.push_back(filtered_setting.value()); + } } } @@ -754,36 +921,52 @@ LeAudioOffloadAudioProvider::getLeAudioBroadcastConfiguration( return ndk::ScopedAStatus::ok(); } - // Match and return the first matched requirement if (in_requirement.subgroupConfigurationRequirements.empty()) { LOG(INFO) << __func__ << ": Empty requirement"; *_aidl_return = filtered_settings[0]; return ndk::ScopedAStatus::ok(); } + // For each subgroup config requirement, find a suitable subgroup config. + // 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) { - // Further filter out bis configuration - LeAudioBroadcastConfigurationSetting filtered_setting(setting); - filtered_setting.subgroupsConfigurations.clear(); - for (auto& sub_cfg : setting.subgroupsConfigurations) { - bool isMatched = false; - for (auto& sub_req : in_requirement.subgroupConfigurationRequirements) { + LeAudioBroadcastConfigurationSetting matched_setting(setting); + matched_setting.subgroupsConfigurations.clear(); + auto total_num_bis = 0; + + bool matched_all_requirement = true; + + 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 and context hint. - isMatched = true; + // Currently will ignore quality matching. + matched_setting.subgroupsConfigurations.push_back(sub_cfg); + total_num_bis += sub_cfg.bisConfigurations.size(); + is_matched = true; + break; + } + // There is an unmatched requirement, this setting cannot be used + if (!is_matched) { + matched_all_requirement = false; break; } - if (isMatched) - filtered_setting.subgroupsConfigurations.push_back(sub_cfg); - } - // Return the first match - if (!filtered_setting.subgroupsConfigurations.empty()) { - LOG(INFO) << __func__ << ": Matched requirement"; - *_aidl_return = filtered_setting; - return ndk::ScopedAStatus::ok(); } + + 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(); } LOG(WARNING) << __func__ << ": Cannot match any requirement"; diff --git a/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.h b/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.h index 2785e7fe1d..06cd405fd7 100644 --- a/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.h +++ b/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.h @@ -96,8 +96,8 @@ class LeAudioOffloadAudioProvider : public BluetoothAudioProvider { // Private matching function definitions bool isMatchedValidCodec(CodecId cfg_codec, CodecId req_codec); - bool isCapabilitiesMatchedContext( - AudioContext setting_context, + bool filterCapabilitiesMatchedContext( + AudioContext& setting_context, const IBluetoothAudioProvider::LeAudioDeviceCapabilities& capabilities); bool isMatchedSamplingFreq( CodecSpecificConfigurationLtv::SamplingFrequency& cfg_freq, @@ -134,11 +134,10 @@ class LeAudioOffloadAudioProvider : public BluetoothAudioProvider { std::vector>& valid_direction_configurations); void filterRequirementAseDirectionConfiguration( - std::vector>& + std::optional>>& direction_configurations, - const std::optional>>& - requirements, - std::vector>& + const std::vector>& requirements, + std::optional>>& valid_direction_configurations); std::optional getCapabilitiesMatchedAseConfigurationSettings( @@ -157,6 +156,14 @@ class LeAudioOffloadAudioProvider : public BluetoothAudioProvider { LeAudioBroadcastConfigurationSetting& setting, const IBluetoothAudioProvider::LeAudioDeviceCapabilities& capabilities); void getBroadcastSettings(); + std::optional getDirectionQosConfiguration( + uint8_t direction, + const IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement& + qosRequirement, + std::vector& ase_configuration_settings); + bool isSubgroupConfigurationMatchedContext( + AudioContext requirement_context, + LeAudioBroadcastSubgroupConfiguration configuration); }; class LeAudioOffloadOutputAudioProvider : public LeAudioOffloadAudioProvider { diff --git a/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp b/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp index c313fb7d0c..ec63831301 100644 --- a/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp +++ b/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp @@ -58,6 +58,7 @@ using aidl::android::hardware::bluetooth::audio::CodecParameters; using aidl::android::hardware::bluetooth::audio::CodecSpecificCapabilitiesLtv; using aidl::android::hardware::bluetooth::audio::CodecSpecificConfigurationLtv; using aidl::android::hardware::bluetooth::audio::CodecType; +using aidl::android::hardware::bluetooth::audio::ConfigurationFlags; using aidl::android::hardware::bluetooth::audio::HfpConfiguration; using aidl::android::hardware::bluetooth::audio::IBluetoothAudioPort; using aidl::android::hardware::bluetooth::audio::IBluetoothAudioProvider; @@ -68,6 +69,7 @@ using aidl::android::hardware::bluetooth::audio::Lc3Configuration; using aidl::android::hardware::bluetooth::audio::LdacCapabilities; using aidl::android::hardware::bluetooth::audio::LdacConfiguration; using aidl::android::hardware::bluetooth::audio::LeAudioAseConfiguration; +using aidl::android::hardware::bluetooth::audio::LeAudioBisConfiguration; using aidl::android::hardware::bluetooth::audio::LeAudioBroadcastConfiguration; using aidl::android::hardware::bluetooth::audio:: LeAudioCodecCapabilitiesSetting; @@ -105,12 +107,24 @@ using AseDirectionConfiguration = IBluetoothAudioProvider:: LeAudioAseConfigurationSetting::AseDirectionConfiguration; using AseQosDirectionRequirement = IBluetoothAudioProvider:: LeAudioAseQosConfigurationRequirement::AseQosDirectionRequirement; +using LeAudioAseQosConfigurationRequirement = + IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement; using LeAudioAseQosConfiguration = IBluetoothAudioProvider::LeAudioAseQosConfiguration; using LeAudioDeviceCapabilities = IBluetoothAudioProvider::LeAudioDeviceCapabilities; using LeAudioConfigurationRequirement = IBluetoothAudioProvider::LeAudioConfigurationRequirement; +using LeAudioBroadcastConfigurationRequirement = + IBluetoothAudioProvider::LeAudioBroadcastConfigurationRequirement; +using LeAudioBroadcastSubgroupConfiguration = + IBluetoothAudioProvider::LeAudioBroadcastSubgroupConfiguration; +using LeAudioBroadcastSubgroupConfigurationRequirement = + IBluetoothAudioProvider::LeAudioBroadcastSubgroupConfigurationRequirement; +using LeAudioBroadcastConfigurationSetting = + IBluetoothAudioProvider::LeAudioBroadcastConfigurationSetting; +using LeAudioSubgroupBisConfiguration = + IBluetoothAudioProvider::LeAudioSubgroupBisConfiguration; // Constants @@ -2228,6 +2242,37 @@ class BluetoothAudioProviderLeAudioOutputHardwareAidl BluetoothAudioProviderFactoryAidl::TearDown(); } + bool IsMultidirectionalCapabilitiesEnabled() { + if (!temp_provider_info_.has_value()) return false; + + return temp_provider_info_.value().supportsMultidirectionalCapabilities; + } + + bool IsAsymmetricConfigurationAllowed() { + if (!temp_provider_info_.has_value()) return false; + if (temp_provider_info_.value().codecInfos.empty()) return false; + + for (auto& codec_info : temp_provider_info_.value().codecInfos) { + if (codec_info.transport.getTag() != CodecInfo::Transport::leAudio) { + return false; + } + + auto flags = + codec_info.transport.get().flags; + + if (!flags) { + continue; + } + + if (flags->bitmask & + ConfigurationFlags::ALLOW_ASYMMETRIC_CONFIGURATIONS) { + return true; + } + } + + return false; + } + bool IsOffloadOutputSupported() { for (auto& capability : temp_provider_capabilities_) { if (capability.getTag() != AudioCapabilities::leAudioCapabilities) { @@ -2284,27 +2329,31 @@ class BluetoothAudioProviderLeAudioOutputHardwareAidl return media_audio_context; } - LeAudioDeviceCapabilities GetDefaultRemoteCapability() { + LeAudioDeviceCapabilities GetDefaultRemoteSinkCapability() { // Create a capability LeAudioDeviceCapabilities capability; capability.codecId = CodecId::Core::LC3; auto pref_context_metadata = MetadataLtv::PreferredAudioContexts(); - pref_context_metadata.values = GetAudioContext(AudioContext::MEDIA); + pref_context_metadata.values = + GetAudioContext(AudioContext::MEDIA | AudioContext::CONVERSATIONAL | + AudioContext::GAME); capability.metadata = {pref_context_metadata}; auto sampling_rate = CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies(); sampling_rate.bitmask = + CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ16000 | CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ8000; auto frame_duration = CodecSpecificCapabilitiesLtv::SupportedFrameDurations(); frame_duration.bitmask = - CodecSpecificCapabilitiesLtv::SupportedFrameDurations::US7500; + CodecSpecificCapabilitiesLtv::SupportedFrameDurations::US7500 | + CodecSpecificCapabilitiesLtv::SupportedFrameDurations::US10000; auto octets = CodecSpecificCapabilitiesLtv::SupportedOctetsPerCodecFrame(); octets.min = 0; - octets.max = 60; + octets.max = 120; auto frames = CodecSpecificCapabilitiesLtv::SupportedMaxCodecFramesPerSDU(); frames.value = 2; capability.codecSpecificCapabilities = {sampling_rate, frame_duration, @@ -2312,29 +2361,394 @@ class BluetoothAudioProviderLeAudioOutputHardwareAidl return capability; } - LeAudioConfigurationRequirement GetDefaultRequirement( - bool is_source_requriement) { + LeAudioDeviceCapabilities GetDefaultRemoteSourceCapability() { + // Create a capability + LeAudioDeviceCapabilities capability; + + capability.codecId = CodecId::Core::LC3; + + auto pref_context_metadata = MetadataLtv::PreferredAudioContexts(); + pref_context_metadata.values = + GetAudioContext(AudioContext::LIVE_AUDIO | + AudioContext::CONVERSATIONAL | AudioContext::GAME); + capability.metadata = {pref_context_metadata}; + + auto sampling_rate = + CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies(); + sampling_rate.bitmask = + CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ16000 | + CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ8000; + auto frame_duration = + CodecSpecificCapabilitiesLtv::SupportedFrameDurations(); + frame_duration.bitmask = + CodecSpecificCapabilitiesLtv::SupportedFrameDurations::US7500 | + CodecSpecificCapabilitiesLtv::SupportedFrameDurations::US10000; + auto octets = CodecSpecificCapabilitiesLtv::SupportedOctetsPerCodecFrame(); + octets.min = 0; + octets.max = 120; + auto frames = CodecSpecificCapabilitiesLtv::SupportedMaxCodecFramesPerSDU(); + frames.value = 2; + capability.codecSpecificCapabilities = {sampling_rate, frame_duration, + octets, frames}; + return capability; + } + + std::optional GetConfigurationLtv( + const std::vector& configurationLtvs, + CodecSpecificConfigurationLtv::Tag tag) { + for (const auto ltv : configurationLtvs) { + if (ltv.getTag() == tag) { + return ltv; + } + } + return std::nullopt; + } + + bool IsAseRequirementSatisfiedWithUnknownChannelCount( + const std::vector>& + ase_requirements, + const std::vector>& + ase_configurations) { + /* This is mandatory to match sample freq, allocation however, when in the + * device group there is only one device which supports left and right + * allocation, and channel count is hidden from the BT stack, the BT stack + * will send single requirement but it can receive two configurations if the + * channel count is 1. + */ + + int num_of_ase_requirements = 0; + for (const auto& ase_req : ase_requirements) { + auto required_allocation_ltv = GetConfigurationLtv( + ase_req->aseConfiguration.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation); + if (required_allocation_ltv == std::nullopt) { + continue; + } + int required_allocation = + required_allocation_ltv + ->get< + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation>() + .bitmask; + num_of_ase_requirements += std::bitset<32>(required_allocation).count(); + } + + int num_of_satisfied_ase_requirements = 0; + for (const auto& ase_req : ase_requirements) { + if (!ase_req) { + continue; + } + auto required_sample_freq_ltv = GetConfigurationLtv( + ase_req->aseConfiguration.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::samplingFrequency); + auto required_allocation_ltv = GetConfigurationLtv( + ase_req->aseConfiguration.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation); + + /* Allocation and sample freq shall be always in the requirement */ + if (!required_sample_freq_ltv || !required_allocation_ltv) { + return false; + } + + int required_allocation = + required_allocation_ltv + ->get< + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation>() + .bitmask; + + for (const auto& ase_conf : ase_configurations) { + if (!ase_conf) { + continue; + } + auto config_sample_freq_ltv = GetConfigurationLtv( + ase_conf->aseConfiguration.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::samplingFrequency); + auto config_allocation_ltv = GetConfigurationLtv( + ase_conf->aseConfiguration.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation); + if (config_sample_freq_ltv == std::nullopt || + config_allocation_ltv == std::nullopt) { + return false; + } + + int configured_allocation = config_allocation_ltv + ->get() + .bitmask; + + if (config_sample_freq_ltv == required_sample_freq_ltv && + (required_allocation & configured_allocation)) { + num_of_satisfied_ase_requirements += + std::bitset<32>(configured_allocation).count(); + } + } + } + + return (num_of_satisfied_ase_requirements == num_of_ase_requirements); + } + + bool IsAseRequirementSatisfied( + const std::vector>& + ase_requirements, + const std::vector>& + ase_configurations) { + // This is mandatory to match sample freq, allocation + int num_of_satisfied_ase_requirements = 0; + + int required_allocations = 0; + for (const auto& ase_req : ase_requirements) { + auto required_allocation_ltv = GetConfigurationLtv( + ase_req->aseConfiguration.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation); + if (required_allocation_ltv == std::nullopt) { + continue; + } + + int allocations = + required_allocation_ltv + ->get< + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation>() + .bitmask; + required_allocations += std::bitset<32>(allocations).count(); + } + + if (ase_requirements.size() != required_allocations) { + /* If more than one allication is requested in the requirement, then use + * different verifier */ + return IsAseRequirementSatisfiedWithUnknownChannelCount( + ase_requirements, ase_configurations); + } + + for (const auto& ase_req : ase_requirements) { + if (!ase_req) { + continue; + } + auto required_sample_freq_ltv = GetConfigurationLtv( + ase_req->aseConfiguration.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::samplingFrequency); + auto required_allocation_ltv = GetConfigurationLtv( + ase_req->aseConfiguration.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation); + + /* Allocation and sample freq shall be always in the requirement */ + if (!required_sample_freq_ltv || !required_allocation_ltv) { + return false; + } + + for (const auto& ase_conf : ase_configurations) { + if (!ase_conf) { + continue; + } + auto config_sample_freq_ltv = GetConfigurationLtv( + ase_conf->aseConfiguration.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::samplingFrequency); + auto config_allocation_ltv = GetConfigurationLtv( + ase_conf->aseConfiguration.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation); + if (config_sample_freq_ltv == std::nullopt || + config_allocation_ltv == std::nullopt) { + return false; + } + + if (config_sample_freq_ltv == required_sample_freq_ltv && + config_allocation_ltv == required_allocation_ltv) { + num_of_satisfied_ase_requirements++; + break; + } + } + } + + return (num_of_satisfied_ase_requirements == ase_requirements.size()); + } + + void VerifyIfRequirementsSatisfied( + const std::vector& requirements, + const std::vector& configurations) { + if (requirements.empty() && configurations.empty()) { + return; + } + + /* It might happen that vendor lib will provide same configuration for + * multiple contexts and it should be accepted + */ + + int num_of_requirements = 0; + for (const auto& req : requirements) { + num_of_requirements += std::bitset<32>(req.audioContext.bitmask).count(); + } + + int num_of_configurations = 0; + for (const auto& conf : configurations) { + num_of_configurations += + std::bitset<32>(conf.audioContext.bitmask).count(); + } + + ASSERT_EQ(num_of_requirements, num_of_configurations); + + int num_of_satisfied_requirements = 0; + for (const auto& req : requirements) { + for (const auto& conf : configurations) { + if ((req.audioContext.bitmask & conf.audioContext.bitmask) != + req.audioContext.bitmask) { + continue; + } + + if (req.sinkAseRequirement && req.sourceAseRequirement) { + if (!conf.sinkAseConfiguration || !conf.sourceAseConfiguration) { + continue; + } + + if (!IsAseRequirementSatisfied(*req.sinkAseRequirement, + *conf.sinkAseConfiguration) || + !IsAseRequirementSatisfied(*req.sourceAseRequirement, + *conf.sourceAseConfiguration)) { + continue; + } + num_of_satisfied_requirements += + std::bitset<32>(req.audioContext.bitmask).count(); + + break; + } else if (req.sinkAseRequirement) { + if (!IsAseRequirementSatisfied(*req.sinkAseRequirement, + *conf.sinkAseConfiguration)) { + continue; + } + num_of_satisfied_requirements += + std::bitset<32>(req.audioContext.bitmask).count(); + break; + } else if (req.sourceAseRequirement) { + if (!IsAseRequirementSatisfied(*req.sourceAseRequirement, + *conf.sourceAseConfiguration)) { + continue; + } + num_of_satisfied_requirements += + std::bitset<32>(req.audioContext.bitmask).count(); + break; + } + } + } + ASSERT_EQ(num_of_satisfied_requirements, num_of_requirements); + } + + LeAudioConfigurationRequirement GetUnicastDefaultRequirement( + int32_t context_bits, bool is_sink_requirement, + bool is_source_requriement, + CodecSpecificConfigurationLtv::SamplingFrequency freq = + CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000) { // Create a requirements LeAudioConfigurationRequirement requirement; - requirement.audioContext = GetAudioContext(AudioContext::MEDIA); + requirement.audioContext = GetAudioContext(context_bits); + + auto allocation = CodecSpecificConfigurationLtv::AudioChannelAllocation(); + allocation.bitmask = + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT | + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT; auto direction_ase_requriement = AseDirectionRequirement(); direction_ase_requriement.aseConfiguration.codecId = CodecId::Core::LC3; direction_ase_requriement.aseConfiguration.targetLatency = LeAudioAseConfiguration::TargetLatency::BALANCED_LATENCY_RELIABILITY; - // Mismatch sampling frequency direction_ase_requriement.aseConfiguration.codecConfiguration = { - CodecSpecificConfigurationLtv::SamplingFrequency::HZ11025, - CodecSpecificConfigurationLtv::FrameDuration::US7500, + freq, CodecSpecificConfigurationLtv::FrameDuration::US10000, allocation + }; + if (is_sink_requirement) + requirement.sinkAseRequirement = {direction_ase_requriement}; + if (is_source_requriement) requirement.sourceAseRequirement = {direction_ase_requriement}; - else - requirement.sinkAseRequirement = {direction_ase_requriement}; + return requirement; } + LeAudioConfigurationRequirement GetUnicastGameRequirement(bool asymmetric) { + // Create a requirements + LeAudioConfigurationRequirement requirement; + requirement.audioContext = GetAudioContext(AudioContext::GAME); + + auto allocation = CodecSpecificConfigurationLtv::AudioChannelAllocation(); + allocation.bitmask = + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT | + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT; + + auto sink_ase_requriement = AseDirectionRequirement(); + sink_ase_requriement.aseConfiguration.codecId = CodecId::Core::LC3; + sink_ase_requriement.aseConfiguration.targetLatency = + LeAudioAseConfiguration::TargetLatency::BALANCED_LATENCY_RELIABILITY; + + sink_ase_requriement.aseConfiguration.codecConfiguration = { + CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, allocation}; + + auto source_ase_requriement = AseDirectionRequirement(); + source_ase_requriement.aseConfiguration.codecId = CodecId::Core::LC3; + source_ase_requriement.aseConfiguration.targetLatency = + LeAudioAseConfiguration::TargetLatency::BALANCED_LATENCY_RELIABILITY; + + if (asymmetric) { + source_ase_requriement.aseConfiguration.codecConfiguration = { + CodecSpecificConfigurationLtv::SamplingFrequency::HZ8000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, allocation}; + } else { + source_ase_requriement.aseConfiguration.codecConfiguration = { + CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, allocation}; + } + + requirement.sinkAseRequirement = {sink_ase_requriement}; + requirement.sourceAseRequirement = {source_ase_requriement}; + + return requirement; + } + + LeAudioAseQosConfigurationRequirement GetQosRequirements( + bool is_sink_requirement, bool is_source_requriement, bool valid = true) { + LeAudioAseQosConfigurationRequirement qosRequirement; + + auto allocation = CodecSpecificConfigurationLtv::AudioChannelAllocation(); + allocation.bitmask = + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT | + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT; + + AseQosDirectionRequirement directionalRequirement = { + .framing = IBluetoothAudioProvider::Framing::UNFRAMED, + .preferredRetransmissionNum = 2, + .maxTransportLatencyMs = 10, + .presentationDelayMinUs = 40000, + .presentationDelayMaxUs = 40000, + .aseConfiguration = + { + .targetLatency = LeAudioAseConfiguration::TargetLatency:: + BALANCED_LATENCY_RELIABILITY, + .codecId = CodecId::Core::LC3, + .codecConfiguration = + {CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, + allocation}, + }, + }; + + if (!valid) { + // clear some required values; + directionalRequirement.maxTransportLatencyMs = 0; + directionalRequirement.presentationDelayMaxUs = 0; + } + + qosRequirement.sinkAseQosRequirement = directionalRequirement; + if (is_source_requriement && is_sink_requirement) { + qosRequirement.sourceAseQosRequirement = directionalRequirement; + qosRequirement.sinkAseQosRequirement = directionalRequirement; + } else if (is_source_requriement) { + qosRequirement.sourceAseQosRequirement = directionalRequirement; + qosRequirement.sinkAseQosRequirement = std::nullopt; + } else if (is_sink_requirement) { + qosRequirement.sourceAseQosRequirement = std::nullopt; + qosRequirement.sinkAseQosRequirement = directionalRequirement; + } + + return qosRequirement; + } + std::vector GetUnicastLc3SupportedList(bool decoding, bool supported) { std::vector le_audio_codec_configs; @@ -2460,6 +2874,11 @@ class BluetoothAudioProviderLeAudioOutputHardwareAidl AudioContext::NOTIFICATIONS, AudioContext::RINGTONE_ALERTS, AudioContext::ALERTS, AudioContext::EMERGENCY_ALARM, }; + + AudioContext bidirectional_contexts = { + .bitmask = AudioContext::CONVERSATIONAL | AudioContext::GAME | + AudioContext::VOICE_ASSISTANTS | AudioContext::LIVE_AUDIO, + }; }; /** @@ -2511,6 +2930,40 @@ TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, BluetoothAudioHalVersion::VERSION_AIDL_V4) { GTEST_SKIP(); } + + if (IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + + std::vector> empty_capability; + std::vector empty_requirement; + std::vector configurations; + + // Check empty capability for source direction + auto aidl_retval = audio_provider_->getLeAudioAseConfiguration( + std::nullopt, empty_capability, empty_requirement, &configurations); + + ASSERT_FALSE(aidl_retval.isOk()); + + // Check empty capability for sink direction + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + empty_capability, std::nullopt, empty_requirement, &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_TRUE(configurations.empty()); +} + +TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, + GetEmptyAseConfigurationEmptyCapability_Multidirectiona) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (!IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + std::vector> empty_capability; std::vector empty_requirement; std::vector configurations; @@ -2536,50 +2989,388 @@ TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, BluetoothAudioHalVersion::VERSION_AIDL_V4) { GTEST_SKIP(); } - std::vector> capabilities = { - GetDefaultRemoteCapability()}; + std::vector> sink_capabilities = { + GetDefaultRemoteSinkCapability()}; + std::vector> source_capabilities = { + GetDefaultRemoteSourceCapability()}; + + auto not_supported_sampling_rate_by_remote = + CodecSpecificConfigurationLtv::SamplingFrequency::HZ11025; // Check empty capability for source direction std::vector configurations; std::vector source_requirements = { - GetDefaultRequirement(true)}; + GetUnicastDefaultRequirement(AudioContext::LIVE_AUDIO, false /*sink */, + true /* source */, + not_supported_sampling_rate_by_remote)}; auto aidl_retval = audio_provider_->getLeAudioAseConfiguration( - std::nullopt, capabilities, source_requirements, &configurations); + std::nullopt, source_capabilities, source_requirements, &configurations); ASSERT_TRUE(aidl_retval.isOk()); ASSERT_TRUE(configurations.empty()); // Check empty capability for sink direction std::vector sink_requirements = { - GetDefaultRequirement(false)}; + GetUnicastDefaultRequirement(AudioContext::MEDIA, true /*sink */, + false /* source */, + not_supported_sampling_rate_by_remote)}; aidl_retval = audio_provider_->getLeAudioAseConfiguration( - capabilities, std::nullopt, source_requirements, &configurations); + sink_capabilities, std::nullopt, sink_requirements, &configurations); ASSERT_TRUE(aidl_retval.isOk()); ASSERT_TRUE(configurations.empty()); } +TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, GetAseConfiguration) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + + std::vector> sink_capabilities = { + GetDefaultRemoteSinkCapability()}; + std::vector> source_capabilities = { + GetDefaultRemoteSourceCapability()}; + + // Should not ask for Source on ENCODING session if Multidiretional not + // supported + std::vector configurations; + std::vector source_requirements = { + GetUnicastDefaultRequirement(AudioContext::LIVE_AUDIO, false /* sink */, + true /* source */)}; + auto aidl_retval = audio_provider_->getLeAudioAseConfiguration( + std::nullopt, source_capabilities, source_requirements, &configurations); + + ASSERT_FALSE(aidl_retval.isOk()); + ASSERT_TRUE(configurations.empty()); + + // Check capability for remote sink direction + std::vector sink_requirements = { + GetUnicastDefaultRequirement(AudioContext::MEDIA, true /* sink */, + false /* source */)}; + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + sink_capabilities, std::nullopt, sink_requirements, &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + VerifyIfRequirementsSatisfied(sink_requirements, configurations); + + // Check multiple capability for remote sink direction + std::vector multi_sink_requirements = { + GetUnicastDefaultRequirement(AudioContext::MEDIA, true /* sink */, + false /* source */), + GetUnicastDefaultRequirement(AudioContext::CONVERSATIONAL, + true /* sink */, false /* source */)}; + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + sink_capabilities, std::nullopt, multi_sink_requirements, + &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + VerifyIfRequirementsSatisfied(multi_sink_requirements, configurations); + + // Check multiple context types in a single requirement. + std::vector multi_context_sink_requirements = + {GetUnicastDefaultRequirement( + AudioContext::MEDIA | AudioContext::SOUND_EFFECTS, true /* sink */, + false /* source */)}; + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + sink_capabilities, std::nullopt, multi_context_sink_requirements, + &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + VerifyIfRequirementsSatisfied(multi_sink_requirements, configurations); +} + +TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, + GetAseConfiguration_Multidirectional) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (!IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + + std::vector> sink_capabilities = { + GetDefaultRemoteSinkCapability()}; + std::vector> source_capabilities = { + GetDefaultRemoteSourceCapability()}; + + // Verify source configuration is received + std::vector configurations; + std::vector source_requirements = { + GetUnicastDefaultRequirement(AudioContext::LIVE_AUDIO, false /* sink */, + true /* source */)}; + auto aidl_retval = audio_provider_->getLeAudioAseConfiguration( + std::nullopt, source_capabilities, source_requirements, &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + VerifyIfRequirementsSatisfied(source_requirements, configurations); + + // Verify sink configuration is received + std::vector sink_requirements = { + GetUnicastDefaultRequirement(AudioContext::MEDIA, true /* sink */, + false /* source */)}; + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + sink_capabilities, std::nullopt, sink_requirements, &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + VerifyIfRequirementsSatisfied(sink_requirements, configurations); + + std::vector combined_requirements = { + GetUnicastDefaultRequirement(AudioContext::LIVE_AUDIO, false /* sink */, + true /* source */), + GetUnicastDefaultRequirement(AudioContext::CONVERSATIONAL, + true /* sink */, true /* source */), + GetUnicastDefaultRequirement(AudioContext::MEDIA, true /* sink */, + false /* source */)}; + + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + sink_capabilities, source_capabilities, combined_requirements, + &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + VerifyIfRequirementsSatisfied(combined_requirements, configurations); +} + +TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, + GetAsymmetricAseConfiguration_Multidirectional) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (!IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + + if (!IsAsymmetricConfigurationAllowed()) { + GTEST_SKIP(); + } + + std::vector configurations; + std::vector> sink_capabilities = { + GetDefaultRemoteSinkCapability()}; + std::vector> source_capabilities = { + GetDefaultRemoteSourceCapability()}; + + std::vector asymmetric_requirements = { + GetUnicastGameRequirement(true /* Asymmetric */)}; + + auto aidl_retval = audio_provider_->getLeAudioAseConfiguration( + sink_capabilities, source_capabilities, asymmetric_requirements, + &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + VerifyIfRequirementsSatisfied(asymmetric_requirements, configurations); +} + +TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, + GetQoSConfiguration_Multidirectional) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (!IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + + auto allocation = CodecSpecificConfigurationLtv::AudioChannelAllocation(); + allocation.bitmask = + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT | + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT; + + LeAudioAseQosConfigurationRequirement requirement = + GetQosRequirements(true, true); + + std::vector + QoSConfigurations; + bool is_supported = false; + for (auto bitmask : all_context_bitmasks) { + requirement.audioContext = GetAudioContext(bitmask); + bool is_bidirectional = bidirectional_contexts.bitmask & bitmask; + + if (is_bidirectional) { + requirement.sourceAseQosRequirement = requirement.sinkAseQosRequirement; + } else { + requirement.sourceAseQosRequirement = std::nullopt; + } + + IBluetoothAudioProvider::LeAudioAseQosConfigurationPair result; + auto aidl_retval = + audio_provider_->getLeAudioAseQosConfiguration(requirement, &result); + if (!aidl_retval.isOk()) { + // If not OK, then it could be not supported, as it is an optional + // feature + ASSERT_EQ(aidl_retval.getExceptionCode(), EX_UNSUPPORTED_OPERATION); + } + + is_supported = true; + if (result.sinkQosConfiguration.has_value()) { + if (is_bidirectional) { + ASSERT_TRUE(result.sourceQosConfiguration.has_value()); + } else { + ASSERT_FALSE(result.sourceQosConfiguration.has_value()); + } + QoSConfigurations.push_back(result.sinkQosConfiguration.value()); + } + } + if (is_supported) { + // QoS Configurations should not be empty, as we searched for all contexts + ASSERT_FALSE(QoSConfigurations.empty()); + } +} + +TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, + GetQoSConfiguration_InvalidRequirements) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + auto allocation = CodecSpecificConfigurationLtv::AudioChannelAllocation(); + allocation.bitmask = + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT | + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT; + + LeAudioAseQosConfigurationRequirement invalid_requirement = + GetQosRequirements(true /* sink */, false /* source */, + false /* valid */); + + std::vector + QoSConfigurations; + for (auto bitmask : all_context_bitmasks) { + invalid_requirement.audioContext = GetAudioContext(bitmask); + IBluetoothAudioProvider::LeAudioAseQosConfigurationPair result; + auto aidl_retval = audio_provider_->getLeAudioAseQosConfiguration( + invalid_requirement, &result); + ASSERT_FALSE(aidl_retval.isOk()); + } +} + TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, GetQoSConfiguration) { if (GetProviderFactoryInterfaceVersion() < BluetoothAudioHalVersion::VERSION_AIDL_V4) { GTEST_SKIP(); } + auto allocation = CodecSpecificConfigurationLtv::AudioChannelAllocation(); + allocation.bitmask = + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT | + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT; + IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement requirement; + requirement = GetQosRequirements(true /* sink */, false /* source */); + std::vector QoSConfigurations; + bool is_supported = false; for (auto bitmask : all_context_bitmasks) { requirement.audioContext = GetAudioContext(bitmask); IBluetoothAudioProvider::LeAudioAseQosConfigurationPair result; auto aidl_retval = audio_provider_->getLeAudioAseQosConfiguration(requirement, &result); - ASSERT_TRUE(aidl_retval.isOk()); - if (result.sinkQosConfiguration.has_value()) - QoSConfigurations.push_back(result.sinkQosConfiguration.value()); - if (result.sourceQosConfiguration.has_value()) - QoSConfigurations.push_back(result.sourceQosConfiguration.value()); + if (!aidl_retval.isOk()) { + // If not OK, then it could be not supported, as it is an optional + // feature + ASSERT_EQ(aidl_retval.getExceptionCode(), EX_UNSUPPORTED_OPERATION); + } else { + is_supported = true; + if (result.sinkQosConfiguration.has_value()) { + QoSConfigurations.push_back(result.sinkQosConfiguration.value()); + } + } + } + + if (is_supported) { + // QoS Configurations should not be empty, as we searched for all contexts + ASSERT_FALSE(QoSConfigurations.empty()); + } +} + +TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, + GetDataPathConfiguration_Multidirectional) { + IBluetoothAudioProvider::StreamConfig sink_requirement; + IBluetoothAudioProvider::StreamConfig source_requirement; + std::vector + DataPathConfigurations; + + if (!IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + + bool is_supported = false; + auto allocation = CodecSpecificConfigurationLtv::AudioChannelAllocation(); + allocation.bitmask = + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT | + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT; + + auto streamMap = LeAudioConfiguration::StreamMap(); + + // Use some mandatory configuration + streamMap.streamHandle = 0x0001; + streamMap.audioChannelAllocation = 0x03; + streamMap.aseConfiguration = { + .targetLatency = + LeAudioAseConfiguration::TargetLatency::BALANCED_LATENCY_RELIABILITY, + .codecId = CodecId::Core::LC3, + .codecConfiguration = + {CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, allocation}, + }; + + // Bidirectional + sink_requirement.streamMap = {streamMap}; + source_requirement.streamMap = {streamMap}; + + for (auto bitmask : all_context_bitmasks) { + sink_requirement.audioContext = GetAudioContext(bitmask); + source_requirement.audioContext = sink_requirement.audioContext; + + IBluetoothAudioProvider::LeAudioDataPathConfigurationPair result; + ::ndk::ScopedAStatus aidl_retval; + + bool is_bidirectional = bidirectional_contexts.bitmask & bitmask; + if (is_bidirectional) { + aidl_retval = audio_provider_->getLeAudioAseDatapathConfiguration( + sink_requirement, source_requirement, &result); + } else { + aidl_retval = audio_provider_->getLeAudioAseDatapathConfiguration( + sink_requirement, std::nullopt, &result); + } + + if (!aidl_retval.isOk()) { + // If not OK, then it could be not supported, as it is an optional + // feature + ASSERT_EQ(aidl_retval.getExceptionCode(), EX_UNSUPPORTED_OPERATION); + } else { + is_supported = true; + if (result.outputConfig.has_value()) { + if (is_bidirectional) { + ASSERT_TRUE(result.inputConfig.has_value()); + } else { + ASSERT_TRUE(!result.inputConfig.has_value()); + } + DataPathConfigurations.push_back(result.outputConfig.value()); + } + } + } + + if (is_supported) { + // Datapath Configurations should not be empty, as we searched for all + // contexts + ASSERT_FALSE(DataPathConfigurations.empty()); } - // QoS Configurations should not be empty, as we searched for all contexts - ASSERT_FALSE(QoSConfigurations.empty()); } TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, @@ -2589,26 +3380,45 @@ TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, GTEST_SKIP(); } IBluetoothAudioProvider::StreamConfig sink_requirement; - IBluetoothAudioProvider::StreamConfig source_requirement; std::vector DataPathConfigurations; bool is_supported = false; + auto allocation = CodecSpecificConfigurationLtv::AudioChannelAllocation(); + allocation.bitmask = + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT | + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT; + + auto streamMap = LeAudioConfiguration::StreamMap(); + + // Use some mandatory configuration + streamMap.streamHandle = 0x0001; + streamMap.audioChannelAllocation = 0x03; + streamMap.aseConfiguration = { + .targetLatency = + LeAudioAseConfiguration::TargetLatency::BALANCED_LATENCY_RELIABILITY, + .codecId = CodecId::Core::LC3, + .codecConfiguration = + {CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, allocation}, + }; + + sink_requirement.streamMap = {streamMap}; for (auto bitmask : all_context_bitmasks) { sink_requirement.audioContext = GetAudioContext(bitmask); - source_requirement.audioContext = GetAudioContext(bitmask); IBluetoothAudioProvider::LeAudioDataPathConfigurationPair result; auto aidl_retval = audio_provider_->getLeAudioAseDatapathConfiguration( - sink_requirement, source_requirement, &result); + sink_requirement, std::nullopt, &result); + if (!aidl_retval.isOk()) { - // If not OK, then it could be not supported, as it is an optional feature + // If not OK, then it could be not supported, as it is an optional + // feature ASSERT_EQ(aidl_retval.getExceptionCode(), EX_UNSUPPORTED_OPERATION); } else { is_supported = true; - if (result.inputConfig.has_value()) - DataPathConfigurations.push_back(result.inputConfig.value()); - if (result.inputConfig.has_value()) - DataPathConfigurations.push_back(result.inputConfig.value()); + if (result.outputConfig.has_value()) { + DataPathConfigurations.push_back(result.outputConfig.value()); + } } } @@ -2655,10 +3465,9 @@ TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, * SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH can be started and * stopped with Unicast hardware encoding config * - * Disabled since offload codec checking is not ready */ TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, - DISABLED_StartAndEndLeAudioOutputSessionWithInvalidAudioConfiguration) { + StartAndEndLeAudioOutputSessionWithInvalidAudioConfiguration) { if (!IsOffloadOutputSupported()) { GTEST_SKIP(); } @@ -2678,8 +3487,8 @@ TEST_P(BluetoothAudioProviderLeAudioOutputHardwareAidl, audio_port_, AudioConfiguration(le_audio_config), latency_modes, &mq_desc); - // AIDL call should fail on invalid codec - ASSERT_FALSE(aidl_retval.isOk()); + // It is OK to start session with invalid configuration + ASSERT_TRUE(aidl_retval.isOk()); EXPECT_TRUE(audio_provider_->endSession().isOk()); } } @@ -2758,8 +3567,8 @@ TEST_P( audio_port_, AudioConfiguration(le_audio_config), latency_modes, &mq_desc); - // AIDL call should fail on invalid codec - ASSERT_FALSE(aidl_retval.isOk()); + // It is OK to start session with invalid configuration + ASSERT_TRUE(aidl_retval.isOk()); EXPECT_TRUE(audio_provider_->endSession().isOk()); } } @@ -2879,10 +3688,9 @@ TEST_P(BluetoothAudioProviderLeAudioInputHardwareAidl, * SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH can be started and * stopped with Unicast hardware encoding config * - * Disabled since offload codec checking is not ready */ TEST_P(BluetoothAudioProviderLeAudioInputHardwareAidl, - DISABLED_StartAndEndLeAudioInputSessionWithInvalidAudioConfiguration) { + StartAndEndLeAudioInputSessionWithInvalidAudioConfiguration) { if (!IsOffloadInputSupported()) { GTEST_SKIP(); } @@ -2903,12 +3711,229 @@ TEST_P(BluetoothAudioProviderLeAudioInputHardwareAidl, audio_port_, AudioConfiguration(le_audio_config), latency_modes, &mq_desc); - // AIDL call should fail on invalid codec - ASSERT_FALSE(aidl_retval.isOk()); + // It is OK to start with invalid configuration as it might be unknown on + // start + ASSERT_TRUE(aidl_retval.isOk()); EXPECT_TRUE(audio_provider_->endSession().isOk()); } } +TEST_P(BluetoothAudioProviderLeAudioInputHardwareAidl, + GetEmptyAseConfigurationEmptyCapability) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + + std::vector> empty_capability; + std::vector empty_requirement; + std::vector configurations; + + // Check success for source direction (Input == DecodingSession == remote + // source) + auto aidl_retval = audio_provider_->getLeAudioAseConfiguration( + std::nullopt, empty_capability, empty_requirement, &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_TRUE(configurations.empty()); + + // Check failure for sink direction + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + empty_capability, std::nullopt, empty_requirement, &configurations); + + ASSERT_FALSE(aidl_retval.isOk()); +} + +TEST_P(BluetoothAudioProviderLeAudioInputHardwareAidl, + GetEmptyAseConfigurationEmptyCapability_Multidirectional) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (!IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + + std::vector> empty_capability; + std::vector empty_requirement; + std::vector configurations; + + // Check empty capability for source direction + auto aidl_retval = audio_provider_->getLeAudioAseConfiguration( + std::nullopt, empty_capability, empty_requirement, &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_TRUE(configurations.empty()); + + // Check empty capability for sink direction + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + empty_capability, std::nullopt, empty_requirement, &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_TRUE(configurations.empty()); +} + +TEST_P(BluetoothAudioProviderLeAudioInputHardwareAidl, GetAseConfiguration) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + + std::vector> sink_capabilities = { + GetDefaultRemoteSinkCapability()}; + std::vector> source_capabilities = { + GetDefaultRemoteSourceCapability()}; + + // Check source configuration is received + std::vector configurations; + std::vector source_requirements = { + GetUnicastDefaultRequirement(AudioContext::LIVE_AUDIO, false /* sink */, + true /* source */)}; + auto aidl_retval = audio_provider_->getLeAudioAseConfiguration( + std::nullopt, source_capabilities, source_requirements, &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + + // Check error result when requesting sink on DECODING session + std::vector sink_requirements = { + GetUnicastDefaultRequirement(AudioContext::MEDIA, true /* sink */, + false /* source */)}; + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + sink_capabilities, std::nullopt, sink_requirements, &configurations); + + ASSERT_FALSE(aidl_retval.isOk()); +} + +TEST_P(BluetoothAudioProviderLeAudioInputHardwareAidl, + GetAseConfiguration_Multidirectional) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (!IsMultidirectionalCapabilitiesEnabled()) { + GTEST_SKIP(); + } + + std::vector> sink_capabilities = { + GetDefaultRemoteSinkCapability()}; + std::vector> source_capabilities = { + GetDefaultRemoteSourceCapability()}; + + // Check source configuration is received + std::vector configurations; + std::vector source_requirements = { + GetUnicastDefaultRequirement(AudioContext::LIVE_AUDIO, false /* sink */, + true /* source */)}; + auto aidl_retval = audio_provider_->getLeAudioAseConfiguration( + std::nullopt, source_capabilities, source_requirements, &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + VerifyIfRequirementsSatisfied(source_requirements, configurations); + + // Check empty capability for sink direction + std::vector sink_requirements = { + GetUnicastDefaultRequirement(AudioContext::MEDIA, true /* sink */, + false /* source */)}; + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + sink_capabilities, std::nullopt, sink_requirements, &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + VerifyIfRequirementsSatisfied(sink_requirements, configurations); + + std::vector combined_requirements = { + GetUnicastDefaultRequirement(AudioContext::LIVE_AUDIO, false /* sink */, + true /* source */), + GetUnicastDefaultRequirement(AudioContext::CONVERSATIONAL, + true /* sink */, true /* source */), + GetUnicastDefaultRequirement(AudioContext::MEDIA, true /* sink */, + false /* source */)}; + + aidl_retval = audio_provider_->getLeAudioAseConfiguration( + sink_capabilities, source_capabilities, combined_requirements, + &configurations); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_FALSE(configurations.empty()); + VerifyIfRequirementsSatisfied(combined_requirements, configurations); +} + +TEST_P(BluetoothAudioProviderLeAudioInputHardwareAidl, + GetQoSConfiguration_InvalidRequirements) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + auto allocation = CodecSpecificConfigurationLtv::AudioChannelAllocation(); + allocation.bitmask = + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT | + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT; + + LeAudioAseQosConfigurationRequirement invalid_requirement = + GetQosRequirements(false /* sink */, true /* source */, + false /* valid */); + + std::vector + QoSConfigurations; + for (auto bitmask : all_context_bitmasks) { + invalid_requirement.audioContext = GetAudioContext(bitmask); + IBluetoothAudioProvider::LeAudioAseQosConfigurationPair result; + auto aidl_retval = audio_provider_->getLeAudioAseQosConfiguration( + invalid_requirement, &result); + ASSERT_FALSE(aidl_retval.isOk()); + } +} + +TEST_P(BluetoothAudioProviderLeAudioInputHardwareAidl, GetQoSConfiguration) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + auto allocation = CodecSpecificConfigurationLtv::AudioChannelAllocation(); + allocation.bitmask = + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT | + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT; + + IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement requirement; + requirement = GetQosRequirements(false /* sink */, true /* source */); + + std::vector + QoSConfigurations; + bool is_supported = false; + for (auto bitmask : all_context_bitmasks) { + requirement.audioContext = GetAudioContext(bitmask); + IBluetoothAudioProvider::LeAudioAseQosConfigurationPair result; + auto aidl_retval = + audio_provider_->getLeAudioAseQosConfiguration(requirement, &result); + if (!aidl_retval.isOk()) { + // If not OK, then it could be not supported, as it is an optional + // feature + ASSERT_EQ(aidl_retval.getExceptionCode(), EX_UNSUPPORTED_OPERATION); + } else { + is_supported = true; + if (result.sourceQosConfiguration.has_value()) { + QoSConfigurations.push_back(result.sourceQosConfiguration.value()); + } + } + } + + if (is_supported) { + // QoS Configurations should not be empty, as we searched for all contexts + ASSERT_FALSE(QoSConfigurations.empty()); + } +} /** * openProvider LE_AUDIO_BROADCAST_SOFTWARE_ENCODING_DATAPATH */ @@ -3058,6 +4083,146 @@ class BluetoothAudioProviderLeAudioBroadcastHardwareAidl return le_audio_codec_configs; } + AudioContext GetAudioContext(int32_t bitmask) { + AudioContext media_audio_context; + media_audio_context.bitmask = bitmask; + return media_audio_context; + } + + std::optional GetConfigurationLtv( + const std::vector& configurationLtvs, + CodecSpecificConfigurationLtv::Tag tag) { + for (const auto ltv : configurationLtvs) { + if (ltv.getTag() == tag) { + return ltv; + } + } + return std::nullopt; + } + + std::optional + GetBisSampleFreq(const LeAudioBisConfiguration& bis_conf) { + auto sample_freq_ltv = GetConfigurationLtv( + bis_conf.codecConfiguration, + CodecSpecificConfigurationLtv::Tag::samplingFrequency); + if (!sample_freq_ltv) { + return std::nullopt; + } + return (*sample_freq_ltv) + .get(); + } + + std::vector + GetSubgroupSampleFreqs( + const LeAudioBroadcastSubgroupConfiguration& subgroup_conf) { + std::vector result = {}; + + for (const auto& bis_conf : subgroup_conf.bisConfigurations) { + auto sample_freq = GetBisSampleFreq(bis_conf.bisConfiguration); + if (sample_freq) { + result.push_back(*sample_freq); + } + } + return result; + } + + void VerifyBroadcastConfiguration( + const LeAudioBroadcastConfigurationRequirement& requirements, + const LeAudioBroadcastConfigurationSetting& configuration, + std::vector + expectedSampleFreqs = {}) { + std::vector sampleFreqs = + {}; + + int number_of_requested_bises = 0; + for (const auto& subgroup_req : + requirements.subgroupConfigurationRequirements) { + number_of_requested_bises += subgroup_req.bisNumPerSubgroup; + } + + if (!expectedSampleFreqs.empty()) { + for (const auto& subgroup_conf : configuration.subgroupsConfigurations) { + auto result = GetSubgroupSampleFreqs(subgroup_conf); + sampleFreqs.insert(sampleFreqs.end(), result.begin(), result.end()); + } + } + + ASSERT_EQ(number_of_requested_bises, configuration.numBis); + ASSERT_EQ(requirements.subgroupConfigurationRequirements.size(), + configuration.subgroupsConfigurations.size()); + + if (expectedSampleFreqs.empty()) { + return; + } + + std::sort(sampleFreqs.begin(), sampleFreqs.end()); + std::sort(expectedSampleFreqs.begin(), expectedSampleFreqs.end()); + + ASSERT_EQ(sampleFreqs, expectedSampleFreqs); + } + + LeAudioDeviceCapabilities GetDefaultBroadcastSinkCapability() { + // Create a capability + LeAudioDeviceCapabilities capability; + + capability.codecId = CodecId::Core::LC3; + + auto pref_context_metadata = MetadataLtv::PreferredAudioContexts(); + pref_context_metadata.values = + GetAudioContext(AudioContext::MEDIA | AudioContext::CONVERSATIONAL | + AudioContext::GAME); + capability.metadata = {pref_context_metadata}; + + auto sampling_rate = + CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies(); + sampling_rate.bitmask = + CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ48000 | + CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ16000; + auto frame_duration = + CodecSpecificCapabilitiesLtv::SupportedFrameDurations(); + frame_duration.bitmask = + CodecSpecificCapabilitiesLtv::SupportedFrameDurations::US7500 | + CodecSpecificCapabilitiesLtv::SupportedFrameDurations::US10000; + auto octets = CodecSpecificCapabilitiesLtv::SupportedOctetsPerCodecFrame(); + octets.min = 0; + octets.max = 120; + auto frames = CodecSpecificCapabilitiesLtv::SupportedMaxCodecFramesPerSDU(); + frames.value = 2; + capability.codecSpecificCapabilities = {sampling_rate, frame_duration, + octets, frames}; + return capability; + } + + LeAudioBroadcastConfigurationRequirement GetBroadcastRequirement( + bool standard_quality, bool high_quality) { + LeAudioBroadcastConfigurationRequirement requirement; + + AudioContext media_audio_context; + media_audio_context.bitmask = AudioContext::MEDIA; + + LeAudioBroadcastSubgroupConfigurationRequirement + standard_quality_requirement = { + .audioContext = media_audio_context, + .quality = IBluetoothAudioProvider::BroadcastQuality::STANDARD, + .bisNumPerSubgroup = 2}; + + LeAudioBroadcastSubgroupConfigurationRequirement high_quality_requirement = + {.audioContext = media_audio_context, + .quality = IBluetoothAudioProvider::BroadcastQuality::HIGH, + .bisNumPerSubgroup = 2}; + + if (standard_quality) { + requirement.subgroupConfigurationRequirements.push_back( + standard_quality_requirement); + } + + if (high_quality) { + requirement.subgroupConfigurationRequirements.push_back( + high_quality_requirement); + } + return requirement; + } + std::vector GetBroadcastLc3SupportedList(bool supported) { std::vector le_audio_codec_configs; if (!supported) { @@ -3164,18 +4329,93 @@ TEST_P(BluetoothAudioProviderLeAudioBroadcastHardwareAidl, BluetoothAudioHalVersion::VERSION_AIDL_V4) { GTEST_SKIP(); } + + if (!IsBroadcastOffloadSupported()) { + GTEST_SKIP(); + } + std::vector> empty_capability; IBluetoothAudioProvider::LeAudioBroadcastConfigurationRequirement empty_requirement; - IBluetoothAudioProvider::LeAudioBroadcastConfigurationSetting* configuration = - new IBluetoothAudioProvider::LeAudioBroadcastConfigurationSetting(); + IBluetoothAudioProvider::LeAudioBroadcastConfigurationSetting configuration; // Check empty capability for source direction auto aidl_retval = audio_provider_->getLeAudioBroadcastConfiguration( - empty_capability, empty_requirement, configuration); + empty_capability, empty_requirement, &configuration); + + ASSERT_FALSE(aidl_retval.isOk()); +} + +TEST_P(BluetoothAudioProviderLeAudioBroadcastHardwareAidl, + GetBroadcastConfigurationEmptyCapability) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (!IsBroadcastOffloadSupported()) { + GTEST_SKIP(); + } + + std::vector> empty_capability; + IBluetoothAudioProvider::LeAudioBroadcastConfigurationSetting configuration; + + IBluetoothAudioProvider::LeAudioBroadcastConfigurationRequirement + one_subgroup_requirement = + GetBroadcastRequirement(true /* standard*/, false /* high */); + + // Check empty capability for source direction + auto aidl_retval = audio_provider_->getLeAudioBroadcastConfiguration( + empty_capability, one_subgroup_requirement, &configuration); ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_NE(configuration.numBis, 0); + ASSERT_FALSE(configuration.subgroupsConfigurations.empty()); + VerifyBroadcastConfiguration(one_subgroup_requirement, configuration); + + IBluetoothAudioProvider::LeAudioBroadcastConfigurationRequirement + two_subgroup_requirement = + GetBroadcastRequirement(true /* standard*/, true /* high */); + + // Check empty capability for source direction + aidl_retval = audio_provider_->getLeAudioBroadcastConfiguration( + empty_capability, two_subgroup_requirement, &configuration); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_NE(configuration.numBis, 0); + ASSERT_FALSE(configuration.subgroupsConfigurations.empty()); + VerifyBroadcastConfiguration(two_subgroup_requirement, configuration); +} + +TEST_P(BluetoothAudioProviderLeAudioBroadcastHardwareAidl, + GetBroadcastConfigurationNonEmptyCapability) { + if (GetProviderFactoryInterfaceVersion() < + BluetoothAudioHalVersion::VERSION_AIDL_V4) { + GTEST_SKIP(); + } + + if (!IsBroadcastOffloadSupported()) { + GTEST_SKIP(); + } + + std::vector> capability = { + GetDefaultBroadcastSinkCapability()}; + + IBluetoothAudioProvider::LeAudioBroadcastConfigurationRequirement + requirement = + GetBroadcastRequirement(true /* standard*/, true /* high */); + + IBluetoothAudioProvider::LeAudioBroadcastConfigurationSetting configuration; + + // Check empty capability for source direction + auto aidl_retval = audio_provider_->getLeAudioBroadcastConfiguration( + capability, requirement, &configuration); + + ASSERT_TRUE(aidl_retval.isOk()); + ASSERT_NE(configuration.numBis, 0); + ASSERT_FALSE(configuration.subgroupsConfigurations.empty()); + VerifyBroadcastConfiguration(requirement, configuration); } /** @@ -3186,7 +4426,7 @@ TEST_P(BluetoothAudioProviderLeAudioBroadcastHardwareAidl, TEST_P(BluetoothAudioProviderLeAudioBroadcastHardwareAidl, StartAndEndLeAudioBroadcastSessionWithPossibleBroadcastConfig) { if (!IsBroadcastOffloadSupported()) { - return; + GTEST_SKIP(); } auto lc3_codec_configs = GetBroadcastLc3SupportedList(true /* supported */); @@ -3225,7 +4465,7 @@ TEST_P( BluetoothAudioProviderLeAudioBroadcastHardwareAidl, DISABLED_StartAndEndLeAudioBroadcastSessionWithInvalidAudioConfiguration) { if (!IsBroadcastOffloadSupported()) { - return; + GTEST_SKIP(); } auto lc3_codec_configs = GetBroadcastLc3SupportedList(false /* supported */);