/* * Copyright 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. */ #include "le_audio_utils.h" #include #include "common/strings.h" #include "le_audio_types.h" #include "os/log.h" using bluetooth::common::ToString; using bluetooth::le_audio::types::AudioContexts; using bluetooth::le_audio::types::LeAudioContextType; namespace fmt { template <> struct formatter : enum_formatter {}; template <> struct formatter : enum_formatter { }; template <> struct formatter : enum_formatter {}; template <> struct formatter : enum_formatter {}; } // namespace fmt namespace bluetooth::le_audio { namespace utils { /* The returned LeAudioContextType should have its entry in the * AudioSetConfigurationProvider's ContextTypeToScenario mapping table. * Otherwise the AudioSetConfigurationProvider will fall back * to default scenario. */ LeAudioContextType AudioContentToLeAudioContext( audio_content_type_t content_type, audio_usage_t usage) { /* Check audio attribute usage of stream */ switch (usage) { case AUDIO_USAGE_MEDIA: return LeAudioContextType::MEDIA; case AUDIO_USAGE_ASSISTANT: return LeAudioContextType::VOICEASSISTANTS; case AUDIO_USAGE_VOICE_COMMUNICATION: case AUDIO_USAGE_CALL_ASSISTANT: return LeAudioContextType::CONVERSATIONAL; case AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING: if (content_type == AUDIO_CONTENT_TYPE_SPEECH) { return LeAudioContextType::CONVERSATIONAL; } if (content_type == AUDIO_CONTENT_TYPE_SONIFICATION) { return LeAudioContextType::RINGTONE; } return LeAudioContextType::MEDIA; case AUDIO_USAGE_GAME: return LeAudioContextType::GAME; case AUDIO_USAGE_NOTIFICATION: return LeAudioContextType::NOTIFICATIONS; case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE: return LeAudioContextType::RINGTONE; case AUDIO_USAGE_ALARM: return LeAudioContextType::ALERTS; case AUDIO_USAGE_EMERGENCY: return LeAudioContextType::EMERGENCYALARM; case AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: return LeAudioContextType::INSTRUCTIONAL; case AUDIO_USAGE_ASSISTANCE_SONIFICATION: return LeAudioContextType::SOUNDEFFECTS; default: break; } return LeAudioContextType::MEDIA; } static std::string usageToString(audio_usage_t usage) { switch (usage) { case AUDIO_USAGE_UNKNOWN: return "USAGE_UNKNOWN"; case AUDIO_USAGE_MEDIA: return "USAGE_MEDIA"; case AUDIO_USAGE_VOICE_COMMUNICATION: return "USAGE_VOICE_COMMUNICATION"; case AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING: return "USAGE_VOICE_COMMUNICATION_SIGNALLING"; case AUDIO_USAGE_ALARM: return "USAGE_ALARM"; case AUDIO_USAGE_NOTIFICATION: return "USAGE_NOTIFICATION"; case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE: return "USAGE_NOTIFICATION_TELEPHONY_RINGTONE"; case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST: return "USAGE_NOTIFICATION_COMMUNICATION_REQUEST"; case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT: return "USAGE_NOTIFICATION_COMMUNICATION_INSTANT"; case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED: return "USAGE_NOTIFICATION_COMMUNICATION_DELAYED"; case AUDIO_USAGE_NOTIFICATION_EVENT: return "USAGE_NOTIFICATION_EVENT"; case AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY: return "USAGE_ASSISTANCE_ACCESSIBILITY"; case AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: return "USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"; case AUDIO_USAGE_ASSISTANCE_SONIFICATION: return "USAGE_ASSISTANCE_SONIFICATION"; case AUDIO_USAGE_GAME: return "USAGE_GAME"; case AUDIO_USAGE_ASSISTANT: return "USAGE_ASSISTANT"; case AUDIO_USAGE_CALL_ASSISTANT: return "USAGE_CALL_ASSISTANT"; case AUDIO_USAGE_EMERGENCY: return "USAGE_EMERGENCY"; case AUDIO_USAGE_SAFETY: return "USAGE_SAFETY"; case AUDIO_USAGE_VEHICLE_STATUS: return "USAGE_VEHICLE_STATUS"; case AUDIO_USAGE_ANNOUNCEMENT: return "USAGE_ANNOUNCEMENT"; default: return "unknown usage "; } } static std::string contentTypeToString(audio_content_type_t content_type) { switch (content_type) { case AUDIO_CONTENT_TYPE_UNKNOWN: return "CONTENT_TYPE_UNKNOWN"; case AUDIO_CONTENT_TYPE_SPEECH: return "CONTENT_TYPE_SPEECH"; case AUDIO_CONTENT_TYPE_MUSIC: return "CONTENT_TYPE_MUSIC"; case AUDIO_CONTENT_TYPE_MOVIE: return "CONTENT_TYPE_MOVIE"; case AUDIO_CONTENT_TYPE_SONIFICATION: return "CONTENT_TYPE_SONIFICATION"; default: return "unknown content type "; } } static const char* audioSourceToStr(audio_source_t source) { const char* strArr[] = { "AUDIO_SOURCE_DEFAULT", "AUDIO_SOURCE_MIC", "AUDIO_SOURCE_VOICE_UPLINK", "AUDIO_SOURCE_VOICE_DOWNLINK", "AUDIO_SOURCE_VOICE_CALL", "AUDIO_SOURCE_CAMCORDER", "AUDIO_SOURCE_VOICE_RECOGNITION", "AUDIO_SOURCE_VOICE_COMMUNICATION", "AUDIO_SOURCE_REMOTE_SUBMIX", "AUDIO_SOURCE_UNPROCESSED", "AUDIO_SOURCE_VOICE_PERFORMANCE"}; if (static_cast(source) < (sizeof(strArr) / sizeof(strArr[0]))) return strArr[source]; return "UNKNOWN"; } static bool isMetadataTagPresent(const char* tags, const char* tag) { std::istringstream iss(tags); std::string t; while (std::getline(iss, t, AUDIO_ATTRIBUTES_TAGS_SEPARATOR)) { log::verbose("Tag {}", t); if (t.compare(tag) == 0) { return true; } } return false; } AudioContexts GetAudioContextsFromSourceMetadata( const std::vector& source_metadata) { AudioContexts track_contexts; for (const auto& entry : source_metadata) { auto track = entry.base; if (track.content_type == 0 && track.usage == 0) continue; log::info("usage={}({}), content_type={}({}), gain={:f}, tag:{}", usageToString(track.usage), track.usage, contentTypeToString(track.content_type), track.content_type, track.gain, entry.tags); if (isMetadataTagPresent(entry.tags, "VX_AOSP_SAMPLESOUND")) { track_contexts.set(LeAudioContextType::SOUNDEFFECTS); } else { track_contexts.set( AudioContentToLeAudioContext(track.content_type, track.usage)); } } return track_contexts; } AudioContexts GetAudioContextsFromSinkMetadata( const std::vector& sink_metadata) { AudioContexts all_track_contexts; for (const auto& entry : sink_metadata) { auto track = entry.base; if (track.source == AUDIO_SOURCE_INVALID) continue; LeAudioContextType track_context; log::debug( "source={}(0x{:02x}), gain={:f}, destination device=0x{:08x}, " "destination device address={:32s}", audioSourceToStr(track.source), track.source, track.gain, track.dest_device, track.dest_device_address); if (track.source == AUDIO_SOURCE_MIC) { track_context = LeAudioContextType::LIVE; } else if (track.source == AUDIO_SOURCE_VOICE_COMMUNICATION) { track_context = LeAudioContextType::CONVERSATIONAL; } else { /* Fallback to voice assistant * This will handle also a case when the device is * AUDIO_SOURCE_VOICE_RECOGNITION */ track_context = LeAudioContextType::VOICEASSISTANTS; log::warn( "Could not match the recording track type to group available " "context. Using context {}.", ToString(track_context)); } all_track_contexts.set(track_context); } if (all_track_contexts.none()) { all_track_contexts = AudioContexts( static_cast::type>( LeAudioContextType::UNSPECIFIED)); log::debug( "Unable to find supported audio source context for the remote audio " "sink device. This may result in voice back channel malfunction."); } log::info("Allowed contexts from sink metadata: {} (0x{:08x})", bluetooth::common::ToString(all_track_contexts), all_track_contexts.value()); return all_track_contexts; } bluetooth::le_audio::btle_audio_codec_index_t translateBluetoothCodecFormatToCodecType(uint8_t codec_format) { switch (codec_format) { case types::kLeAudioCodingFormatLC3: return bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3; } return bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_INVALID; } bluetooth::le_audio::btle_audio_sample_rate_index_t translateToBtLeAudioCodecConfigSampleRate(uint32_t sample_rate) { log::info("{}", sample_rate); switch (sample_rate) { case LeAudioCodecConfiguration::kSampleRate8000: return LE_AUDIO_SAMPLE_RATE_INDEX_8000HZ; case LeAudioCodecConfiguration::kSampleRate16000: return LE_AUDIO_SAMPLE_RATE_INDEX_16000HZ; case LeAudioCodecConfiguration::kSampleRate24000: return LE_AUDIO_SAMPLE_RATE_INDEX_24000HZ; case LeAudioCodecConfiguration::kSampleRate32000: return LE_AUDIO_SAMPLE_RATE_INDEX_32000HZ; case LeAudioCodecConfiguration::kSampleRate44100: return LE_AUDIO_SAMPLE_RATE_INDEX_44100HZ; case LeAudioCodecConfiguration::kSampleRate48000: return LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ; } return LE_AUDIO_SAMPLE_RATE_INDEX_NONE; } bluetooth::le_audio::btle_audio_bits_per_sample_index_t translateToBtLeAudioCodecConfigBitPerSample(uint8_t bits_per_sample) { switch (bits_per_sample) { case 16: return bluetooth::le_audio::LE_AUDIO_BITS_PER_SAMPLE_INDEX_16; case 24: return bluetooth::le_audio::LE_AUDIO_BITS_PER_SAMPLE_INDEX_24; case 32: return bluetooth::le_audio::LE_AUDIO_BITS_PER_SAMPLE_INDEX_32; } return bluetooth::le_audio::LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE; } bluetooth::le_audio::btle_audio_channel_count_index_t translateToBtLeAudioCodecConfigChannelCount(uint8_t channel_count) { switch (channel_count) { case 1: return bluetooth::le_audio::LE_AUDIO_CHANNEL_COUNT_INDEX_1; case 2: return bluetooth::le_audio::LE_AUDIO_CHANNEL_COUNT_INDEX_2; } return bluetooth::le_audio::LE_AUDIO_CHANNEL_COUNT_INDEX_NONE; } bluetooth::le_audio::btle_audio_frame_duration_index_t translateToBtLeAudioCodecConfigFrameDuration(int frame_duration) { switch (frame_duration) { case 7500: return bluetooth::le_audio::LE_AUDIO_FRAME_DURATION_INDEX_7500US; case 10000: return bluetooth::le_audio::LE_AUDIO_FRAME_DURATION_INDEX_10000US; } return bluetooth::le_audio::LE_AUDIO_FRAME_DURATION_INDEX_NONE; } void fillStreamParamsToBtLeAudioCodecConfig( const std::vector& confs, bluetooth::le_audio::btle_audio_codec_config_t& out_config) { if (confs.size() == 0) { log::warn("Stream params are null"); return; } auto config = confs.at(0).codec; out_config.codec_type = translateBluetoothCodecFormatToCodecType(config.id.coding_format); if (out_config.codec_type != bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3) { return; } out_config.sample_rate = translateToBtLeAudioCodecConfigSampleRate( config.GetSamplingFrequencyHz()); out_config.bits_per_sample = translateToBtLeAudioCodecConfigBitPerSample(16); out_config.frame_duration = translateToBtLeAudioCodecConfigFrameDuration(config.GetDataIntervalUs()); out_config.octets_per_frame = config.GetOctectsPerFrame(); out_config.channel_count = translateToBtLeAudioCodecConfigChannelCount( config.GetChannelCountPerIsoStream()); } static bool is_known_codec(const types::LeAudioCodecId& codec_id) { switch (codec_id.coding_format) { case types::kLeAudioCodingFormatLC3: return true; } return false; } static void fillRemotePacsCapabitiliesToBtLeAudioCodecConfig( const struct types::acs_ac_record& record, std::vector& vec) { if (!utils::IsCodecUsingLtvFormat(record.codec_id)) { log::warn( "Unknown codec capability format. Unable to report known codec " "parameters."); return; } log::assert_that(!record.codec_spec_caps.IsEmpty(), "Codec specific capabilities are not parsed approprietly."); const struct types::LeAudioCoreCodecCapabilities capa = record.codec_spec_caps.GetAsCoreCodecCapabilities(); for (uint8_t freq_bit = codec_spec_conf::kLeAudioSamplingFreq8000Hz; freq_bit <= codec_spec_conf::kLeAudioSamplingFreq384000Hz; freq_bit++) { if (!capa.IsSamplingFrequencyConfigSupported(freq_bit)) continue; for (uint8_t fd_bit = codec_spec_conf::kLeAudioCodecFrameDur7500us; fd_bit <= codec_spec_conf::kLeAudioCodecFrameDur10000us; fd_bit++) { if (!capa.IsFrameDurationConfigSupported(fd_bit)) continue; if (!capa.HasSupportedAudioChannelCounts()) { bluetooth::le_audio::btle_audio_codec_config_t config = { .codec_type = utils::translateBluetoothCodecFormatToCodecType( record.codec_id.coding_format), .sample_rate = utils::translateToBtLeAudioCodecConfigSampleRate( types::LeAudioCoreCodecConfig::GetSamplingFrequencyHz( freq_bit)), .bits_per_sample = utils::translateToBtLeAudioCodecConfigBitPerSample(16), .channel_count = utils::translateToBtLeAudioCodecConfigChannelCount(1), .frame_duration = utils::translateToBtLeAudioCodecConfigFrameDuration( types::LeAudioCoreCodecConfig::GetFrameDurationUs(fd_bit)), }; vec.push_back(config); } else { for (int chan_bit = 1; chan_bit <= 2; chan_bit++) { if (!capa.IsAudioChannelCountsSupported(chan_bit)) continue; bluetooth::le_audio::btle_audio_codec_config_t config = { .codec_type = utils::translateBluetoothCodecFormatToCodecType( record.codec_id.coding_format), .sample_rate = utils::translateToBtLeAudioCodecConfigSampleRate( types::LeAudioCoreCodecConfig::GetSamplingFrequencyHz( freq_bit)), .bits_per_sample = utils::translateToBtLeAudioCodecConfigBitPerSample(16), .channel_count = utils::translateToBtLeAudioCodecConfigChannelCount(chan_bit), .frame_duration = utils::translateToBtLeAudioCodecConfigFrameDuration( types::LeAudioCoreCodecConfig::GetFrameDurationUs( fd_bit)), }; vec.push_back(config); } } } } } std::vector GetRemoteBtLeAudioCodecConfigFromPac( const types::PublishedAudioCapabilities& group_pacs) { std::vector vec; for (auto& [handles, pacs_record] : group_pacs) { for (auto& pac : pacs_record) { if (!is_known_codec(pac.codec_id)) continue; fillRemotePacsCapabitiliesToBtLeAudioCodecConfig(pac, vec); } } return vec; } bool IsCodecUsingLtvFormat(const types::LeAudioCodecId& codec_id) { if (codec_id == set_configurations::LeAudioCodecIdLc3) { return true; } return false; } ::bluetooth::le_audio::LeAudioCodecConfiguration GetAudioSessionCodecConfigFromAudioSetConfiguration( const bluetooth::le_audio::set_configurations::AudioSetConfiguration& audio_set_conf, uint8_t remote_direction) { /* Note: For now we expect that each ASE in a particular direction needs * exactly the same audio codec parameters. */ LeAudioCodecConfiguration group_config = {0, 0, 0, 0}; for (const auto& conf : audio_set_conf.confs.get(remote_direction)) { if (group_config.sample_rate != 0 && conf.codec.GetSamplingFrequencyHz() != group_config.sample_rate) { log::warn( "Stream configuration could not be determined (multiple, different " "sampling frequencies) for remote_direction: {:#x}", remote_direction); break; } group_config.sample_rate = conf.codec.GetSamplingFrequencyHz(); if (group_config.data_interval_us != 0 && conf.codec.GetDataIntervalUs() != group_config.data_interval_us) { log::warn( "Stream configuration could not be determined (multiple, different " "data intervals) for remote_direction: {:#x}", remote_direction); break; } group_config.data_interval_us = conf.codec.GetDataIntervalUs(); if (group_config.bits_per_sample != 0 && conf.codec.GetBitsPerSample() != group_config.bits_per_sample) { log::warn( "Stream configuration could not be determined (multiple, different " "bits per sample) for remote_direction: {:#x}", remote_direction); break; } group_config.bits_per_sample = conf.codec.GetBitsPerSample(); group_config.num_channels += conf.codec.GetChannelCountPerIsoStream(); } if (group_config.num_channels > 2) group_config.num_channels = 2; return group_config; } types::LeAudioConfigurationStrategy GetStrategyForAseConfig( const std::vector& cfgs, uint8_t device_cnt) { if (cfgs.size() == 0) { return types::LeAudioConfigurationStrategy::RFU; } /* Banded headphones or the Classic TWS style topology (a single device) */ if (device_cnt == 1) { if (cfgs.at(0).codec.GetChannelCountPerIsoStream() == 1) { /* One mono ASE - could be a single channel microphone */ if (cfgs.size() == 1) { return types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE; } /* Each channel on a dedicated ASE - TWS style split channel re-routing */ return types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE; } /* Banded headphones with 1 ASE - requires two channels per CIS */ return types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE; } // We need at least 2 ASEs in the group config to set up more than one device if (cfgs.size() == 1) { return types::LeAudioConfigurationStrategy::RFU; } /* The common one channel per device topology */ return types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE; } static bool IsCodecConfigSupported(const types::LeAudioLtvMap& pacs, const types::LeAudioLtvMap& reqs, uint8_t channel_cnt_per_ase) { auto caps = pacs.GetAsCoreCodecCapabilities(); auto config = reqs.GetAsCoreCodecConfig(); /* Sampling frequency */ if (!caps.HasSupportedSamplingFrequencies() || !config.sampling_frequency) { log::debug("Missing supported sampling frequencies capability"); return false; } if (!caps.IsSamplingFrequencyConfigSupported( config.sampling_frequency.value())) { log::debug("Cfg: SamplingFrequency= {:#x}", config.sampling_frequency.value()); log::debug("Cap: SupportedSamplingFrequencies= {:#x}", caps.supported_sampling_frequencies.value()); log::debug("Sampling frequency not supported"); return false; } /* Channel counts */ if (!caps.IsAudioChannelCountsSupported(channel_cnt_per_ase)) { log::debug("Cfg: Allocated channel count= {:#x}", channel_cnt_per_ase); log::debug("Cap: Supported channel counts= {:#x}", caps.supported_audio_channel_counts.value_or(1)); log::debug("Channel count not supported"); return false; } /* Frame duration */ if (!caps.HasSupportedFrameDurations() || !config.frame_duration) { log::debug("Missing supported frame durations capability"); return false; } if (!caps.IsFrameDurationConfigSupported(config.frame_duration.value())) { log::debug("Cfg: FrameDuration= {:#x}", config.frame_duration.value()); log::debug("Cap: SupportedFrameDurations= {:#x}", caps.supported_frame_durations.value()); log::debug("Frame duration not supported"); return false; } /* Octets per frame */ if (!caps.HasSupportedOctetsPerCodecFrame() || !config.octets_per_codec_frame) { log::debug("Missing supported octets per codec frame"); return false; } if (!caps.IsOctetsPerCodecFrameConfigSupported( config.octets_per_codec_frame.value())) { log::debug("Cfg: Octets per frame={}", config.octets_per_codec_frame.value()); log::debug("Cap: Min octets per frame={}", caps.supported_min_octets_per_codec_frame.value()); log::debug("Cap: Max octets per frame={}", caps.supported_max_octets_per_codec_frame.value()); log::debug("Octets per codec frame outside the capabilities"); return false; } return true; } static bool IsCodecConfigSettingSupported( const types::acs_ac_record& pac, const set_configurations::CodecConfigSetting& codec_config_setting) { const auto& codec_id = codec_config_setting.id; if (codec_id != pac.codec_id) return false; log::debug(": Settings for format: 0x{:#02x} ", codec_id.coding_format); if (utils::IsCodecUsingLtvFormat(codec_id)) { log::assert_that( !pac.codec_spec_caps.IsEmpty(), "Codec specific capabilities are not parsed approprietly."); return IsCodecConfigSupported( pac.codec_spec_caps, codec_config_setting.params, codec_config_setting.GetChannelCountPerIsoStream()); } log::error("Codec {}, seems to be not supported here.", bluetooth::common::ToString(codec_id)); return false; } const struct types::acs_ac_record* GetConfigurationSupportedPac( const types::PublishedAudioCapabilities& pacs, const set_configurations::CodecConfigSetting& codec_config_setting) { for (const auto& pac_tuple : pacs) { for (const auto& pac : std::get<1>(pac_tuple)) { if (utils::IsCodecConfigSettingSupported(pac, codec_config_setting)) return &pac; }; } /* Doesn't match required configuration with any PAC */ if (pacs.size() == 0) log::error("No PAC records"); return nullptr; } } // namespace utils } // namespace bluetooth::le_audio