Files
hardware_interfaces/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.cpp
Bao Do 649569202b New parsing logic and configuration matching for LE Audio
Match the supported allocation channels from the device's capability
and remove loose matching ability.
This helps with choosing the correct configuration respecting
the device's number of ASE.

We also add "name" field for easier debugging.

Bug: 331490291
Test: atest VtsHalBluetoothAudioTargetTest
Change-Id: Iefd406fcd913ce553a02242b0a2100cab431476e
2024-07-18 16:59:48 +08:00

1459 lines
57 KiB
C++

/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "BTAudioProviderLeAudioHW"
#include "LeAudioOffloadAudioProvider.h"
#include <BluetoothAudioCodecs.h>
#include <BluetoothAudioSessionReport.h>
#include <android-base/logging.h>
namespace aidl {
namespace android {
namespace hardware {
namespace bluetooth {
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<CodecSpecificConfigurationLtv::SamplingFrequency, uint32_t>
freq_to_support_bitmask_map = {
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ8000,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ8000},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ11025,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ11025},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ16000},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ22050,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ22050},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ24000,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ24000},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ32000,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ32000},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ48000,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ48000},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ88200,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ88200},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ96000,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ96000},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ176400,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ176400},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ192000,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ192000},
{CodecSpecificConfigurationLtv::SamplingFrequency::HZ384000,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ384000},
};
// Helper map from capability's tag to configuration's tag
std::map<CodecSpecificCapabilitiesLtv::Tag, CodecSpecificConfigurationLtv::Tag>
cap_to_cfg_tag_map = {
{CodecSpecificCapabilitiesLtv::Tag::supportedSamplingFrequencies,
CodecSpecificConfigurationLtv::Tag::samplingFrequency},
{CodecSpecificCapabilitiesLtv::Tag::supportedMaxCodecFramesPerSDU,
CodecSpecificConfigurationLtv::Tag::codecFrameBlocksPerSDU},
{CodecSpecificCapabilitiesLtv::Tag::supportedFrameDurations,
CodecSpecificConfigurationLtv::Tag::frameDuration},
{CodecSpecificCapabilitiesLtv::Tag::supportedAudioChannelCounts,
CodecSpecificConfigurationLtv::Tag::audioChannelAllocation},
{CodecSpecificCapabilitiesLtv::Tag::supportedOctetsPerCodecFrame,
CodecSpecificConfigurationLtv::Tag::octetsPerCodecFrame},
};
const std::map<CodecSpecificConfigurationLtv::FrameDuration, uint32_t>
fduration_to_support_fduration_map = {
{CodecSpecificConfigurationLtv::FrameDuration::US7500,
CodecSpecificCapabilitiesLtv::SupportedFrameDurations::US7500},
{CodecSpecificConfigurationLtv::FrameDuration::US10000,
CodecSpecificCapabilitiesLtv::SupportedFrameDurations::US10000},
};
std::map<int32_t, CodecSpecificConfigurationLtv::SamplingFrequency>
sampling_freq_map = {
{16000, CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000},
{24000, CodecSpecificConfigurationLtv::SamplingFrequency::HZ24000},
{48000, CodecSpecificConfigurationLtv::SamplingFrequency::HZ48000},
{96000, CodecSpecificConfigurationLtv::SamplingFrequency::HZ96000},
};
std::map<int32_t, CodecSpecificConfigurationLtv::FrameDuration>
frame_duration_map = {
{7500, CodecSpecificConfigurationLtv::FrameDuration::US7500},
{10000, CodecSpecificConfigurationLtv::FrameDuration::US10000},
};
LeAudioOffloadOutputAudioProvider::LeAudioOffloadOutputAudioProvider()
: LeAudioOffloadAudioProvider() {
session_type_ = SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH;
}
LeAudioOffloadInputAudioProvider::LeAudioOffloadInputAudioProvider()
: LeAudioOffloadAudioProvider() {
session_type_ = SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH;
}
LeAudioOffloadBroadcastAudioProvider::LeAudioOffloadBroadcastAudioProvider()
: LeAudioOffloadAudioProvider() {
session_type_ =
SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH;
}
LeAudioOffloadAudioProvider::LeAudioOffloadAudioProvider()
: BluetoothAudioProvider() {}
bool LeAudioOffloadAudioProvider::isValid(const SessionType& sessionType) {
return (sessionType == session_type_);
}
std::string getSettingOutputString(
IBluetoothAudioProvider::LeAudioAseConfigurationSetting& setting) {
std::stringstream ss;
std::string name = "";
if (!setting.sinkAseConfiguration.has_value() &&
!setting.sourceAseConfiguration.has_value())
return "";
std::vector<
std::optional<LeAudioAseConfigurationSetting::AseDirectionConfiguration>>*
directionAseConfiguration;
if (setting.sinkAseConfiguration.has_value() &&
!setting.sinkAseConfiguration.value().empty())
directionAseConfiguration = &setting.sinkAseConfiguration.value();
else
directionAseConfiguration = &setting.sourceAseConfiguration.value();
for (auto& aseConfiguration : *directionAseConfiguration) {
if (aseConfiguration.has_value() &&
aseConfiguration.value().aseConfiguration.metadata.has_value()) {
for (auto& meta :
aseConfiguration.value().aseConfiguration.metadata.value()) {
if (meta.has_value() &&
meta.value().getTag() == MetadataLtv::vendorSpecific) {
auto k = meta.value().get<MetadataLtv::vendorSpecific>().opaqueValue;
name = std::string(k.begin(), k.end());
break;
}
}
}
}
ss << "setting name: " << name << ", setting: " << setting.toString();
return ss.str();
}
ndk::ScopedAStatus LeAudioOffloadAudioProvider::startSession(
const std::shared_ptr<IBluetoothAudioPort>& host_if,
const AudioConfiguration& audio_config,
const std::vector<LatencyMode>& latency_modes, DataMQDesc* _aidl_return) {
if (session_type_ ==
SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH) {
if (audio_config.getTag() != AudioConfiguration::leAudioBroadcastConfig) {
LOG(WARNING) << __func__ << " - Invalid Audio Configuration="
<< audio_config.toString();
*_aidl_return = DataMQDesc();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
} else if (audio_config.getTag() != AudioConfiguration::leAudioConfig) {
LOG(WARNING) << __func__ << " - Invalid Audio Configuration="
<< audio_config.toString();
*_aidl_return = DataMQDesc();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
return BluetoothAudioProvider::startSession(host_if, audio_config,
latency_modes, _aidl_return);
}
ndk::ScopedAStatus LeAudioOffloadAudioProvider::onSessionReady(
DataMQDesc* _aidl_return) {
BluetoothAudioSessionReport::OnSessionStarted(
session_type_, stack_iface_, nullptr, *audio_config_, latency_modes_);
*_aidl_return = DataMQDesc();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus LeAudioOffloadAudioProvider::setCodecPriority(
const CodecId& in_codecId, int32_t in_priority) {
codec_priority_map_[in_codecId] = in_priority;
return ndk::ScopedAStatus::ok();
};
bool LeAudioOffloadAudioProvider::isMatchedValidCodec(CodecId cfg_codec,
CodecId req_codec) {
auto priority = codec_priority_map_.find(cfg_codec);
if (priority != codec_priority_map_.end() &&
priority->second ==
LeAudioOffloadAudioProvider::CODEC_PRIORITY_DISABLED) {
return false;
}
return cfg_codec == req_codec;
}
bool LeAudioOffloadAudioProvider::filterCapabilitiesMatchedContext(
AudioContext& setting_context,
const IBluetoothAudioProvider::LeAudioDeviceCapabilities& capabilities) {
// If has no metadata, assume match
if (!capabilities.metadata.has_value()) return true;
for (auto metadata : capabilities.metadata.value()) {
if (!metadata.has_value()) continue;
if (metadata.value().getTag() == MetadataLtv::Tag::preferredAudioContexts) {
// Check all pref audio context to see if anything matched
auto& prefer_context =
metadata.value()
.get<MetadataLtv::Tag::preferredAudioContexts>()
.values;
if (setting_context.bitmask & prefer_context.bitmask) {
// New mask with matched capability
setting_context.bitmask &= prefer_context.bitmask;
return true;
}
}
}
return false;
}
bool LeAudioOffloadAudioProvider::isMatchedSamplingFreq(
CodecSpecificConfigurationLtv::SamplingFrequency& cfg_freq,
CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies&
capability_freq) {
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;
}
bool LeAudioOffloadAudioProvider::isMatchedFrameDuration(
CodecSpecificConfigurationLtv::FrameDuration& cfg_fduration,
CodecSpecificCapabilitiesLtv::SupportedFrameDurations&
capability_fduration) {
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;
}
int getCountFromBitmask(int bitmask) {
return std::bitset<32>(bitmask).count();
}
bool LeAudioOffloadAudioProvider::isMatchedAudioChannel(
CodecSpecificConfigurationLtv::AudioChannelAllocation& cfg_channel,
CodecSpecificCapabilitiesLtv::SupportedAudioChannelCounts&
capability_channel) {
int count = getCountFromBitmask(cfg_channel.bitmask);
if (count == 1 &&
!(capability_channel.bitmask &
CodecSpecificCapabilitiesLtv::SupportedAudioChannelCounts::ONE))
return false;
if (count == 2 &&
!(capability_channel.bitmask &
CodecSpecificCapabilitiesLtv::SupportedAudioChannelCounts::TWO))
return false;
return true;
}
bool LeAudioOffloadAudioProvider::isMatchedCodecFramesPerSDU(
CodecSpecificConfigurationLtv::CodecFrameBlocksPerSDU& cfg_frame_sdu,
CodecSpecificCapabilitiesLtv::SupportedMaxCodecFramesPerSDU&
capability_frame_sdu) {
return cfg_frame_sdu.value <= capability_frame_sdu.value;
}
bool LeAudioOffloadAudioProvider::isMatchedOctetsPerCodecFrame(
CodecSpecificConfigurationLtv::OctetsPerCodecFrame& cfg_octets,
CodecSpecificCapabilitiesLtv::SupportedOctetsPerCodecFrame&
capability_octets) {
return cfg_octets.value >= capability_octets.min &&
cfg_octets.value <= capability_octets.max;
}
bool LeAudioOffloadAudioProvider::isCapabilitiesMatchedCodecConfiguration(
std::vector<CodecSpecificConfigurationLtv>& codec_cfg,
std::vector<CodecSpecificCapabilitiesLtv> codec_capabilities) {
// Convert all codec_cfg into a map of tags -> correct data
std::map<CodecSpecificConfigurationLtv::Tag, CodecSpecificConfigurationLtv>
cfg_tag_map;
for (auto codec_cfg_data : codec_cfg)
cfg_tag_map[codec_cfg_data.getTag()] = codec_cfg_data;
for (auto& codec_capability : codec_capabilities) {
auto cfg = cfg_tag_map.find(cap_to_cfg_tag_map[codec_capability.getTag()]);
// If capability has this tag, but our configuration doesn't
// Then we will assume it is matched
if (cfg == cfg_tag_map.end()) {
continue;
}
switch (codec_capability.getTag()) {
case CodecSpecificCapabilitiesLtv::Tag::supportedSamplingFrequencies: {
if (!isMatchedSamplingFreq(
cfg->second.get<
CodecSpecificConfigurationLtv::Tag::samplingFrequency>(),
codec_capability.get<CodecSpecificCapabilitiesLtv::Tag::
supportedSamplingFrequencies>())) {
return false;
}
break;
}
case CodecSpecificCapabilitiesLtv::Tag::supportedFrameDurations: {
if (!isMatchedFrameDuration(
cfg->second
.get<CodecSpecificConfigurationLtv::Tag::frameDuration>(),
codec_capability.get<CodecSpecificCapabilitiesLtv::Tag::
supportedFrameDurations>())) {
return false;
}
break;
}
case CodecSpecificCapabilitiesLtv::Tag::supportedAudioChannelCounts: {
if (!isMatchedAudioChannel(
cfg->second.get<CodecSpecificConfigurationLtv::Tag::
audioChannelAllocation>(),
codec_capability.get<CodecSpecificCapabilitiesLtv::Tag::
supportedAudioChannelCounts>())) {
return false;
}
break;
}
case CodecSpecificCapabilitiesLtv::Tag::supportedMaxCodecFramesPerSDU: {
if (!isMatchedCodecFramesPerSDU(
cfg->second.get<CodecSpecificConfigurationLtv::Tag::
codecFrameBlocksPerSDU>(),
codec_capability.get<CodecSpecificCapabilitiesLtv::Tag::
supportedMaxCodecFramesPerSDU>())) {
return false;
}
break;
}
case CodecSpecificCapabilitiesLtv::Tag::supportedOctetsPerCodecFrame: {
if (!isMatchedOctetsPerCodecFrame(
cfg->second.get<
CodecSpecificConfigurationLtv::Tag::octetsPerCodecFrame>(),
codec_capability.get<CodecSpecificCapabilitiesLtv::Tag::
supportedOctetsPerCodecFrame>())) {
return false;
}
break;
}
}
}
return true;
}
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())) {
return false;
}
}
if (requirement_cfg.targetLatency !=
LeAudioAseConfiguration::TargetLatency::UNDEFINED &&
setting_cfg.targetLatency != requirement_cfg.targetLatency) {
return false;
}
// Ignore PHY requirement
// Check all codec configuration
std::map<CodecSpecificConfigurationLtv::Tag, CodecSpecificConfigurationLtv>
cfg_tag_map;
for (auto cfg : setting_cfg.codecConfiguration)
cfg_tag_map[cfg.getTag()] = cfg;
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()) {
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) {
return false;
}
}
// Ignore vendor configuration and metadata requirement
return true;
}
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)) {
return false;
}
return true;
}
void LeAudioOffloadAudioProvider::filterCapabilitiesAseDirectionConfiguration(
std::vector<std::optional<AseDirectionConfiguration>>&
direction_configurations,
const IBluetoothAudioProvider::LeAudioDeviceCapabilities& capabilities,
std::vector<std::optional<AseDirectionConfiguration>>&
valid_direction_configurations) {
for (auto direction_configuration : direction_configurations) {
if (!direction_configuration.has_value()) continue;
if (!direction_configuration.value().aseConfiguration.codecId.has_value())
continue;
if (!isMatchedValidCodec(
direction_configuration.value().aseConfiguration.codecId.value(),
capabilities.codecId))
continue;
// Check matching for codec configuration <=> codec capabilities
if (!isCapabilitiesMatchedCodecConfiguration(
direction_configuration.value().aseConfiguration.codecConfiguration,
capabilities.codecSpecificCapabilities))
continue;
valid_direction_configurations.push_back(direction_configuration);
}
}
int getLeAudioAseConfigurationAllocationBitmask(LeAudioAseConfiguration cfg) {
for (auto cfg_ltv : cfg.codecConfiguration) {
if (cfg_ltv.getTag() ==
CodecSpecificConfigurationLtv::Tag::audioChannelAllocation) {
return cfg_ltv
.get<CodecSpecificConfigurationLtv::Tag::audioChannelAllocation>()
.bitmask;
}
}
return 0;
}
std::optional<AseDirectionConfiguration> findValidMonoConfig(
std::vector<AseDirectionConfiguration>& 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<CodecSpecificConfigurationLtv::Tag::audioChannelAllocation>()
.bitmask = bitmask;
return cfg;
}
}
}
}
return std::nullopt;
}
std::vector<AseDirectionConfiguration> getValidConfigurationsFromAllocation(
int req_allocation_bitmask,
std::vector<AseDirectionConfiguration>& 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<AseDirectionConfiguration> 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 {};
}
// Check and filter each index to see if it's a match.
void LeAudioOffloadAudioProvider::filterRequirementAseDirectionConfiguration(
std::optional<std::vector<std::optional<AseDirectionConfiguration>>>&
direction_configurations,
const std::vector<std::optional<AseDirectionRequirement>>& requirements,
std::optional<std::vector<std::optional<AseDirectionConfiguration>>>&
valid_direction_configurations) {
if (!direction_configurations.has_value()) return;
if (!valid_direction_configurations.has_value()) {
valid_direction_configurations =
std::vector<std::optional<AseDirectionConfiguration>>();
}
// Exact matching process
// Need to respect the number of device
for (int i = 0; i < requirements.size(); ++i) {
auto requirement = requirements[i];
auto direction_configuration = direction_configurations.value()[i];
if (!direction_configuration.has_value()) {
valid_direction_configurations = std::nullopt;
return;
}
auto cfg = direction_configuration.value();
if (!filterMatchedAseConfiguration(cfg.aseConfiguration,
requirement.value().aseConfiguration)) {
valid_direction_configurations = std::nullopt;
return; // No way to match
}
// For exact match, we require this direction to have the same allocation.
// If stereo, need stereo.
// If mono, need mono (modified to the correct required allocation)
auto req_allocation_bitmask = getLeAudioAseConfigurationAllocationBitmask(
requirement.value().aseConfiguration);
int req_channel_count = getCountFromBitmask(req_allocation_bitmask);
int cfg_bitmask =
getLeAudioAseConfigurationAllocationBitmask(cfg.aseConfiguration);
int cfg_channel_count = getCountFromBitmask(cfg_bitmask);
if (req_channel_count <= 1) {
// MONO case, is a match if also mono, modify to the same allocation
if (cfg_channel_count > 1) {
valid_direction_configurations = std::nullopt;
return; // Not a match
}
// 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<CodecSpecificConfigurationLtv::Tag::audioChannelAllocation>()
.bitmask = req_allocation_bitmask;
break;
}
}
} else {
// STEREO case, is a match if same allocation
if (req_allocation_bitmask != cfg_bitmask) {
valid_direction_configurations = std::nullopt;
return; // Not a match
}
}
// Push to list if valid
valid_direction_configurations.value().push_back(cfg);
}
}
/* Get a new LeAudioAseConfigurationSetting by matching a setting with a
* capabilities. The new setting will have a filtered list of
* AseDirectionConfiguration that matched the capabilities */
std::optional<LeAudioAseConfigurationSetting>
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,
};
// Get a list of all matched AseDirectionConfiguration
// for the input direction
std::vector<std::optional<AseDirectionConfiguration>>*
direction_configuration = nullptr;
if (direction == kLeAudioDirectionSink) {
if (!filtered_setting.sinkAseConfiguration.has_value()) return std::nullopt;
direction_configuration = &filtered_setting.sinkAseConfiguration.value();
} else {
if (!filtered_setting.sourceAseConfiguration.has_value())
return std::nullopt;
direction_configuration = &filtered_setting.sourceAseConfiguration.value();
}
std::vector<std::optional<AseDirectionConfiguration>>
valid_direction_configuration;
filterCapabilitiesAseDirectionConfiguration(
*direction_configuration, capabilities, valid_direction_configuration);
// No valid configuration for this direction
if (valid_direction_configuration.empty()) {
return std::nullopt;
}
// Create a new LeAudioAseConfigurationSetting and return
// For other direction will contain all settings
if (direction == kLeAudioDirectionSink) {
filtered_setting.sinkAseConfiguration = valid_direction_configuration;
} else {
filtered_setting.sourceAseConfiguration = valid_direction_configuration;
}
return filtered_setting;
}
/* Get a new LeAudioAseConfigurationSetting by matching a setting with a
* requirement. The new setting will have a filtered list of
* AseDirectionConfiguration that matched the requirement */
std::optional<LeAudioAseConfigurationSetting>
LeAudioOffloadAudioProvider::getRequirementMatchedAseConfigurationSettings(
IBluetoothAudioProvider::LeAudioAseConfigurationSetting& setting,
const IBluetoothAudioProvider::LeAudioConfigurationRequirement&
requirement) {
// Try to match context in metadata.
if ((setting.audioContext.bitmask & requirement.audioContext.bitmask) !=
requirement.audioContext.bitmask)
return std::nullopt;
// Further filter setting's context
setting.audioContext.bitmask &= requirement.audioContext.bitmask;
// Create a new LeAudioAseConfigurationSetting to return
LeAudioAseConfigurationSetting filtered_setting{
.audioContext = setting.audioContext,
.packing = setting.packing,
.flags = setting.flags,
};
// The number of AseDirectionRequirement in the requirement
// is the number of device.
// The exact matching process is as follow:
// 1. Setting direction has the same number of cfg (ignore when null require)
// 2. For each index, it's a 1-1 filter / mapping.
if (requirement.sinkAseRequirement.has_value() &&
requirement.sinkAseRequirement.value().size() !=
setting.sinkAseConfiguration.value().size()) {
return std::nullopt;
}
if (requirement.sourceAseRequirement.has_value() &&
requirement.sourceAseRequirement.value().size() !=
setting.sourceAseConfiguration.value().size()) {
return std::nullopt;
}
if (requirement.sinkAseRequirement.has_value()) {
filterRequirementAseDirectionConfiguration(
setting.sinkAseConfiguration, requirement.sinkAseRequirement.value(),
filtered_setting.sinkAseConfiguration);
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()) {
return std::nullopt;
}
}
return filtered_setting;
}
std::vector<IBluetoothAudioProvider::LeAudioAseConfigurationSetting>
LeAudioOffloadAudioProvider::matchWithRequirement(
std::vector<IBluetoothAudioProvider::LeAudioAseConfigurationSetting>&
matched_ase_configuration_settings,
const std::vector<IBluetoothAudioProvider::LeAudioConfigurationRequirement>&
in_requirements) {
// Each requirement will match with a valid setting
std::vector<IBluetoothAudioProvider::LeAudioAseConfigurationSetting> 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);
if (filtered_ase_configuration_setting.has_value()) {
result.push_back(filtered_ase_configuration_setting.value());
LOG(INFO) << __func__ << ": Result found: "
<< getSettingOutputString(
filtered_ase_configuration_setting.value());
// 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 the sink capability (if presented)
// - AND matched with the source capability (if presented)
// - and the setting need to pass the requirement
ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseConfiguration(
const std::optional<std::vector<
std::optional<IBluetoothAudioProvider::LeAudioDeviceCapabilities>>>&
in_remoteSinkAudioCapabilities,
const std::optional<std::vector<
std::optional<IBluetoothAudioProvider::LeAudioDeviceCapabilities>>>&
in_remoteSourceAudioCapabilities,
const std::vector<IBluetoothAudioProvider::LeAudioConfigurationRequirement>&
in_requirements,
std::vector<IBluetoothAudioProvider::LeAudioAseConfigurationSetting>*
_aidl_return) {
// Get all configuration settings
std::vector<IBluetoothAudioProvider::LeAudioAseConfigurationSetting>
ase_configuration_settings =
BluetoothAudioCodecs::GetLeAudioAseConfigurationSettings();
if (!in_remoteSinkAudioCapabilities.has_value() &&
!in_remoteSourceAudioCapabilities.has_value()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
// 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<IBluetoothAudioProvider::LeAudioAseConfigurationSetting>
sink_matched_ase_configuration_settings;
std::vector<IBluetoothAudioProvider::LeAudioAseConfigurationSetting>
matched_ase_configuration_settings;
// Matched ASE configuration with non-preferred audio context
std::vector<IBluetoothAudioProvider::LeAudioAseConfigurationSetting>
sink_non_prefer_matched_ase_configuration_settings;
std::vector<IBluetoothAudioProvider::LeAudioAseConfigurationSetting>
non_prefer_matched_ase_configuration_settings;
// A setting must match both source and sink.
// First filter all setting matched with sink capability
if (in_remoteSinkAudioCapabilities.has_value()) {
for (auto& setting : ase_configuration_settings)
for (auto& capability : in_remoteSinkAudioCapabilities.value()) {
if (!capability.has_value()) continue;
// LOG(DEBUG) << __func__ << ": " << capability.value().toString();
auto filtered_ase_configuration_setting =
getCapabilitiesMatchedAseConfigurationSettings(
setting, capability.value(), kLeAudioDirectionSink);
if (filtered_ase_configuration_setting.has_value()) {
// Push to non-prefer first for the broadest matching possible
sink_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())) {
sink_matched_ase_configuration_settings.push_back(
filtered_ase_configuration_setting.value());
}
}
}
} else {
sink_matched_ase_configuration_settings = ase_configuration_settings;
sink_non_prefer_matched_ase_configuration_settings =
ase_configuration_settings;
}
// Combine filter every source capability
if (in_remoteSourceAudioCapabilities.has_value()) {
// Prefer context
for (auto& setting : sink_matched_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()) {
// 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());
}
}
}
// Non prefer context
for (auto& setting : sink_non_prefer_matched_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()) {
// Push to non-prefer first for the broadest matching possible
non_prefer_matched_ase_configuration_settings.push_back(
filtered_ase_configuration_setting.value());
}
}
} else {
matched_ase_configuration_settings =
sink_matched_ase_configuration_settings;
non_prefer_matched_ase_configuration_settings =
sink_non_prefer_matched_ase_configuration_settings;
}
// Matching priority list:
// Preferred context - exact match with allocation
// Any context - exact match with allocation
LOG(DEBUG) << __func__ << ": Called with requirement: ";
for (auto& requirement : in_requirements) {
LOG(DEBUG) << __func__ << " requirement: " << requirement.toString();
}
LOG(DEBUG) << __func__ << ": List of settings with the same context:";
for (auto& setting : matched_ase_configuration_settings) {
LOG(DEBUG) << __func__ << ": " << getSettingOutputString(setting);
}
auto result =
matchWithRequirement(matched_ase_configuration_settings, in_requirements);
if (result.empty()) {
LOG(WARNING) << __func__
<< ": Cannot match with preferred context settings";
result = matchWithRequirement(non_prefer_matched_ase_configuration_settings,
in_requirements);
}
if (result.empty()) {
LOG(ERROR) << __func__
<< ": Cannot match with non-preferred context settings";
}
*_aidl_return = result;
return ndk::ScopedAStatus::ok();
};
bool LeAudioOffloadAudioProvider::isMatchedQosRequirement(
LeAudioAseQosConfiguration setting_qos,
AseQosDirectionRequirement requirement_qos) {
if (setting_qos.retransmissionNum !=
requirement_qos.preferredRetransmissionNum) {
return false;
}
if (setting_qos.maxTransportLatencyMs >
requirement_qos.maxTransportLatencyMs) {
return false;
}
return true;
}
bool isValidQosRequirement(AseQosDirectionRequirement qosRequirement) {
return ((qosRequirement.maxTransportLatencyMs > 0) &&
(qosRequirement.presentationDelayMaxUs > 0) &&
(qosRequirement.presentationDelayMaxUs >=
qosRequirement.presentationDelayMinUs));
}
std::optional<LeAudioAseQosConfiguration>
LeAudioOffloadAudioProvider::getDirectionQosConfiguration(
uint8_t direction,
const IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement&
qosRequirement,
std::vector<LeAudioAseConfigurationSetting>& ase_configuration_settings,
bool is_exact) {
std::optional<AseQosDirectionRequirement> direction_qos_requirement =
std::nullopt;
// 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.bitmask & qosRequirement.audioContext.bitmask) !=
qosRequirement.audioContext.bitmask)
continue;
// Match configuration flags
// Currently configuration flags are not populated, ignore.
// Get a list of all matched AseDirectionConfiguration
// for the input direction
std::optional<std::vector<std::optional<AseDirectionConfiguration>>>
direction_configuration = std::nullopt;
if (direction == kLeAudioDirectionSink) {
if (!setting.sinkAseConfiguration.has_value()) continue;
direction_configuration.emplace(setting.sinkAseConfiguration.value());
} else {
if (!setting.sourceAseConfiguration.has_value()) continue;
direction_configuration.emplace(setting.sourceAseConfiguration.value());
}
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<AseDirectionConfiguration>();
for (auto& cfg : direction_configuration.value()) {
if (!cfg.has_value()) continue;
// If no requirement, return the first QoS
if (!direction_qos_requirement.has_value()) {
return cfg.value().qosConfiguration;
}
// If has requirement, return the first matched QoS
// Try to match the ASE configuration
// and QoS with requirement
if (!cfg.value().qosConfiguration.has_value()) continue;
if (filterMatchedAseConfiguration(
cfg.value().aseConfiguration,
direction_qos_requirement.value().aseConfiguration) &&
isMatchedQosRequirement(cfg.value().qosConfiguration.value(),
direction_qos_requirement.value())) {
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;
}
ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseQosConfiguration(
const IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement&
in_qosRequirement,
IBluetoothAudioProvider::LeAudioAseQosConfigurationPair* _aidl_return) {
IBluetoothAudioProvider::LeAudioAseQosConfigurationPair result;
// Get all configuration settings
std::vector<IBluetoothAudioProvider::LeAudioAseConfigurationSetting>
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);
{
// 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, true);
if (!result.sourceQosConfiguration.has_value()) {
result.sourceQosConfiguration = getDirectionQosConfiguration(
kLeAudioDirectionSource, in_qosRequirement,
ase_configuration_settings, false);
}
}
*_aidl_return = result;
return ndk::ScopedAStatus::ok();
};
ndk::ScopedAStatus LeAudioOffloadAudioProvider::onSinkAseMetadataChanged(
IBluetoothAudioProvider::AseState in_state, int32_t /*in_cigId*/,
int32_t /*in_cisId*/,
const std::optional<std::vector<std::optional<MetadataLtv>>>& in_metadata) {
(void)in_state;
(void)in_metadata;
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
};
ndk::ScopedAStatus LeAudioOffloadAudioProvider::onSourceAseMetadataChanged(
IBluetoothAudioProvider::AseState in_state, int32_t /*in_cigId*/,
int32_t /*in_cisId*/,
const std::optional<std::vector<std::optional<MetadataLtv>>>& in_metadata) {
(void)in_state;
(void)in_metadata;
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<CodecSpecificConfigurationLtv::audioChannelAllocation>()
.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 basic broadcast settings from provider info";
std::vector<CodecInfo> 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 population
CodecSpecificConfigurationLtv::AudioChannelAllocation default_allocation;
default_allocation.bitmask =
CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_CENTER;
CodecSpecificConfigurationLtv::CodecFrameBlocksPerSDU default_frame;
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<CodecInfo::Transport::leAudio>();
LeAudioBroadcastConfigurationSetting setting;
setting.retransmitionNum = 4;
setting.maxTransportLatencyMs = 60;
setting.sduIntervalUs = 10000;
setting.maxSduOctets = 40;
// Default setting
setting.numBis = 1;
setting.phy = {Phy::TWO_M};
// Populate BIS configuration info using codec_info
LeAudioBisConfiguration bis_cfg;
bis_cfg.codecId = codec_info.id;
CodecSpecificConfigurationLtv::OctetsPerCodecFrame octets;
octets.value = transport.bitdepth[0];
bis_cfg.codecConfiguration = {
sampling_freq_map[transport.samplingFrequencyHz[0]],
octets,
frame_duration_map[transport.frameDurationUs[0]],
default_allocation,
default_frame,
};
// Ignore bis_cfg.metadata
// Add information to structure
IBluetoothAudioProvider::LeAudioSubgroupBisConfiguration sub_bis_cfg;
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};
setting.subgroupsConfigurations = {sub_cfg};
broadcast_settings.push_back(setting);
}
LOG(INFO) << __func__
<< ": Done loading broadcast settings from provider info";
}
/* Get a new LeAudioAseConfigurationSetting by matching a setting with a
* capabilities. The new setting will have a filtered list of
* AseDirectionConfiguration that matched the capabilities */
std::optional<LeAudioBroadcastConfigurationSetting>
LeAudioOffloadAudioProvider::
getCapabilitiesMatchedBroadcastConfigurationSettings(
LeAudioBroadcastConfigurationSetting& setting,
const IBluetoothAudioProvider::LeAudioDeviceCapabilities&
capabilities) {
std::vector<IBluetoothAudioProvider::LeAudioBroadcastSubgroupConfiguration>
filter_subgroup;
for (auto& sub_cfg : setting.subgroupsConfigurations) {
std::vector<IBluetoothAudioProvider::LeAudioSubgroupBisConfiguration>
filtered_bis_cfg;
for (auto& bis_cfg : sub_cfg.bisConfigurations)
if (isMatchedBISConfiguration(bis_cfg.bisConfiguration, capabilities)) {
filtered_bis_cfg.push_back(bis_cfg);
}
if (!filtered_bis_cfg.empty()) {
IBluetoothAudioProvider::LeAudioBroadcastSubgroupConfiguration
subgroup_cfg;
subgroup_cfg.bisConfigurations = filtered_bis_cfg;
filter_subgroup.push_back(subgroup_cfg);
}
}
if (filter_subgroup.empty()) return std::nullopt;
// Create a new LeAudioAseConfigurationSetting and return
LeAudioBroadcastConfigurationSetting filtered_setting(setting);
filtered_setting.subgroupsConfigurations = filter_subgroup;
return filtered_setting;
}
std::vector<CodecSpecificConfigurationLtv> getCodecRequirementBasedOnContext(
int context_bitmask, IBluetoothAudioProvider::BroadcastQuality quality) {
// Default requirement: lc3_stereo_16_2
std::vector<CodecSpecificConfigurationLtv> 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 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<AudioContext> config_context = std::nullopt;
auto codec_requirement =
getCodecRequirementBasedOnContext(requirement_context.bitmask, quality);
std::map<CodecSpecificConfigurationLtv::Tag, CodecSpecificConfigurationLtv>
req_tag_map;
for (auto x : codec_requirement) req_tag_map[x.getTag()] = x;
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
LeAudioOffloadAudioProvider::getLeAudioBroadcastConfiguration(
const std::optional<std::vector<
std::optional<IBluetoothAudioProvider::LeAudioDeviceCapabilities>>>&
in_remoteSinkAudioCapabilities,
const IBluetoothAudioProvider::LeAudioBroadcastConfigurationRequirement&
in_requirement,
LeAudioBroadcastConfigurationSetting* _aidl_return) {
if (in_requirement.subgroupConfigurationRequirements.empty()) {
LOG(WARNING) << __func__ << ": Empty requirement";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
// Broadcast setting are from provider info
// We will allow empty capability input, match all settings with requirements.
getBroadcastSettings();
std::vector<LeAudioBroadcastConfigurationSetting> filtered_settings;
if (!in_remoteSinkAudioCapabilities.has_value() ||
in_remoteSinkAudioCapabilities.value().empty()) {
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());
}
}
}
if (filtered_settings.empty()) {
LOG(WARNING) << __func__ << ": Cannot match any remote capability";
return ndk::ScopedAStatus::ok();
}
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.
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 = {};
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);
}
if (is_matched) {
is_setting_matched = true;
for (auto& sub_group_config : setting.subgroupsConfigurations)
return_setting.subgroupsConfigurations.push_back(sub_group_config);
break;
}
}
if (!is_setting_matched) {
LOG(WARNING) << __func__
<< ": Cannot find a setting that match requirement "
<< sub_req.toString();
return ndk::ScopedAStatus::ok();
}
}
// 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<Phy>(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();
};
} // namespace audio
} // namespace bluetooth
} // namespace hardware
} // namespace android
} // namespace aidl