/* * Copyright (C) 2022 BlueKitchen GmbH * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * 4. Any redistribution, use, or modification is done solely for * personal benefit and not for any commercial purpose or for * monetary gain. * * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Please inquire about commercial licensing options at * contact@bluekitchen-gmbh.com * */ #define BTSTACK_FILE__ "le_audio_util.c" #include "btstack_util.h" #include "btstack_debug.h" #include "le-audio/le_audio_util.h" #include "btstack_event.h" static const le_audio_codec_configuration_t codec_specific_config_settings[] = { {"8_1", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_8000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US, 26}, {"8_2", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_8000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US, 30}, {"16_1", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_16000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US, 30}, {"16_2", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_16000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US, 40}, {"24_1", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_24000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US, 45}, {"24_2", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_24000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US, 60}, {"32_1", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_32000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US, 60}, {"32_2", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_32000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US, 80}, {"441_1",LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_44100_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US, 97}, {"441_2",LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_44100_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US, 130}, {"48_1", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_48000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US, 75}, {"48_2", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_48000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US, 100}, {"48_3", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_48000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US, 90}, {"48_4", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_48000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US, 120}, {"48_5", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_48000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US, 117}, {"48_6", LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_48000_HZ, LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US, 155}, }; static const le_audio_qos_configuration_t qos_config_settings[] = { {"8_1_1", 7500, 0, 26, 2, 8}, {"8_2_1", 10000, 0, 30, 2, 10}, {"16_1_1", 7500, 0, 30, 2, 8}, {"16_2_1", 10000, 0, 40, 2, 10}, {"24_1_1", 7500, 0, 45, 2, 8}, {"24_2_1", 10000, 0, 60, 2, 10}, {"32_1_1", 7500, 0, 60, 2, 8}, {"32_2_1", 10000, 0, 80, 2, 10}, {"441_1_1", 8163, 1, 97, 5, 24}, {"441_2_1", 10884, 1, 130, 5, 31}, {"48_1_1", 7500, 0, 75, 5, 15}, {"48_2_1", 10000, 0, 100, 5, 20}, {"48_3_1", 7500, 0, 90, 5, 15}, {"48_4_1", 10000, 0, 120, 5, 20}, {"48_5_1", 7500, 0, 117, 5, 15}, {"48_6_1", 10000, 0, 115, 5, 20}, {"8_1_2", 7500, 0, 26, 13, 75}, {"8_2_2", 10000, 0, 30, 13, 95}, {"16_1_2", 7500, 0, 30, 13, 75}, {"16_2_2", 10000, 0, 40, 13, 95}, {"24_1_2", 7500, 0, 45, 13, 75}, {"24_2_2", 10000, 0, 60, 13, 95}, {"32_1_2", 7500, 0, 60, 13, 75}, {"32_2_2", 10000, 0, 80, 13, 95}, {"441_1_2", 8163, 1, 97, 13, 80}, {"441_2_2", 10884, 1, 130, 13, 85}, {"48_1_2", 7500, 0, 75, 13, 75}, {"48_2_2", 10000, 0, 100, 13, 95}, {"48_3_2", 7500, 0, 90, 13, 75}, {"48_4_2", 10000, 0, 120, 13, 100}, {"48_5_2", 7500, 0, 117, 13, 75}, {"48_6_2", 10000, 0, 115, 13, 100} }; static uint8_t codec_offset(le_audio_codec_sampling_frequency_index_t sampling_frequency_index, le_audio_codec_frame_duration_index_t frame_duration_index, le_audio_quality_t audio_quality){ btstack_assert(audio_quality <= LE_AUDIO_QUALITY_HIGH); if (sampling_frequency_index == LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_48000_HZ){ return 10 + (audio_quality - LE_AUDIO_QUALITY_LOW) * 2 + (frame_duration_index - LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US); } return (sampling_frequency_index - LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_8000_HZ) * 2 + (frame_duration_index - LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US); } const le_audio_codec_configuration_t * le_audio_util_get_codec_setting( le_audio_codec_sampling_frequency_index_t sampling_frequency_index, le_audio_codec_frame_duration_index_t frame_duration_index, le_audio_quality_t audio_quality){ return &codec_specific_config_settings[codec_offset(sampling_frequency_index, frame_duration_index, audio_quality)]; } const le_audio_qos_configuration_t * le_audio_util_get_qos_setting( le_audio_codec_sampling_frequency_index_t sampling_frequency_index, le_audio_codec_frame_duration_index_t frame_duration_index, le_audio_quality_t audio_quality, uint8_t num_channels){ btstack_assert((num_channels >= 1) && (num_channels <= 2)); return &qos_config_settings[(num_channels - 1) * 16 + codec_offset(sampling_frequency_index, frame_duration_index, audio_quality)]; } // help with buffer == NULL uint16_t le_audio_util_virtual_memcpy_helper( const uint8_t * field_data, uint16_t field_len, uint16_t field_offset, uint8_t * buffer, uint16_t buffer_size, uint16_t buffer_offset){ // only calc total size if (buffer == NULL) { return field_len; } return btstack_virtual_memcpy(field_data, field_len, field_offset, buffer, buffer_size, buffer_offset); } uint16_t le_audio_util_metadata_virtual_memcpy(const le_audio_metadata_t * metadata, uint8_t metadata_length, uint16_t * records_offset, uint8_t * buffer, uint16_t buffer_size, uint16_t buffer_offset){ uint16_t metadata_type; uint8_t field_data[7]; uint16_t stored_bytes = 0; uint16_t metadata_length_pos = *records_offset; field_data[0] = 0; stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 1, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += 1; if (metadata_length == 0){ return stored_bytes; } for (metadata_type = (uint16_t)LE_AUDIO_METADATA_TYPE_PREFERRED_AUDIO_CONTEXTS; metadata_type < (uint16_t) LE_AUDIO_METADATA_TYPE_RFU; metadata_type++){ if ((metadata->metadata_mask & (1 << metadata_type) ) != 0 ){ // reserve field_data[0] for num butes to store field_data[0] = 1; field_data[1] = metadata_type; switch ((le_audio_metadata_type_t)metadata_type){ case LE_AUDIO_METADATA_TYPE_PREFERRED_AUDIO_CONTEXTS: field_data[0] += 2; little_endian_store_16(field_data, 2, metadata->preferred_audio_contexts_mask); stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, field_data[0] + 1, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += field_data[0] + 1; break; case LE_AUDIO_METADATA_TYPE_STREAMING_AUDIO_CONTEXTS: field_data[0] += 2; little_endian_store_16(field_data, 2, metadata->streaming_audio_contexts_mask); stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, field_data[0] + 1, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += field_data[0] + 1; break; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO: field_data[0] += metadata->program_info_length; stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 2, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += 2; stored_bytes += le_audio_util_virtual_memcpy_helper(metadata->program_info, metadata->program_info_length, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += metadata->program_info_length; break; case LE_AUDIO_METADATA_TYPE_LANGUAGE: field_data[0] += 3; little_endian_store_24(field_data, 2, metadata->language_code); stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, field_data[0] + 1, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += field_data[0] + 1; break; case LE_AUDIO_METADATA_TYPE_CCID_LIST: field_data[0] += metadata->ccids_num; stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 2, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += 2; stored_bytes += le_audio_util_virtual_memcpy_helper(metadata->ccids, metadata->ccids_num, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += metadata->ccids_num; break; case LE_AUDIO_METADATA_TYPE_PARENTAL_RATING: field_data[0] += 1; field_data[2] = (uint8_t) metadata->parental_rating; stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, field_data[0] + 1, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += field_data[0] + 1; break; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI: field_data[0] += metadata->program_info_uri_length; stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 2, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += 2; stored_bytes += le_audio_util_virtual_memcpy_helper(metadata->program_info_uri, metadata->program_info_uri_length, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += metadata->program_info_uri_length; break; case LE_AUDIO_METADATA_TYPE_MAPPED_EXTENDED_METADATA_BIT_POSITION: field_data[0] += 2 + metadata->extended_metadata_length; field_data[1] = LE_AUDIO_METADATA_TYPE_EXTENDED_METADATA; little_endian_store_16(field_data, 2, metadata->extended_metadata_type); stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 4, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += 4; stored_bytes += le_audio_util_virtual_memcpy_helper(metadata->extended_metadata, metadata->extended_metadata_length, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += metadata->extended_metadata_length; break; case LE_AUDIO_METADATA_TYPE_MAPPED_VENDOR_SPECIFIC_METADATA_BIT_POSITION: field_data[0] += 2 + metadata->vendor_specific_metadata_length; field_data[1] = LE_AUDIO_METADATA_TYPE_VENDOR_SPECIFIC_METADATA; little_endian_store_16(field_data, 2, metadata->vendor_specific_company_id); stored_bytes += le_audio_util_virtual_memcpy_helper(field_data, 4, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += 4; stored_bytes += le_audio_util_virtual_memcpy_helper(metadata->vendor_specific_metadata, metadata->vendor_specific_metadata_length, *records_offset, buffer, buffer_size, buffer_offset); *records_offset += metadata->vendor_specific_metadata_length; break; default: btstack_assert(false); break; } } } field_data[0] = *records_offset - metadata_length_pos - 1; le_audio_util_virtual_memcpy_helper(field_data, 1, metadata_length_pos, buffer, buffer_size, buffer_offset); return stored_bytes; } // parse metadata, first unsupported type is stored in metadata.unsupported_type uint16_t le_audio_util_metadata_parse(const uint8_t *buffer, uint8_t buffer_size, le_audio_metadata_t * metadata){ // reset capabilities memset(metadata, 0, sizeof(le_audio_metadata_t)); // parse config to get sampling frequency and frame duration uint8_t offset = 0; uint8_t metadata_config_length = buffer[offset++]; if (buffer_size < metadata_config_length){ return 0; } metadata->metadata_mask = 0; while ((offset + 1) < metadata_config_length){ uint8_t ltv_len = buffer[offset++]; le_audio_metadata_type_t ltv_type = (le_audio_metadata_type_t)buffer[offset]; le_audio_parental_rating_t parental_rating; switch (ltv_type){ case LE_AUDIO_METADATA_TYPE_PREFERRED_AUDIO_CONTEXTS: metadata->preferred_audio_contexts_mask = little_endian_read_16(buffer, offset+1); metadata->metadata_mask |= (1 << ltv_type); break; case LE_AUDIO_METADATA_TYPE_STREAMING_AUDIO_CONTEXTS: metadata->streaming_audio_contexts_mask = little_endian_read_16(buffer, offset+1); metadata->metadata_mask |= (1 << ltv_type); break; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO: metadata->program_info_length = btstack_min(ltv_len, LE_AUDIO_PROGRAM_INFO_MAX_LENGTH); memcpy(metadata->program_info, &buffer[offset+1], metadata->program_info_length); metadata->metadata_mask |= (1 << ltv_type); break; case LE_AUDIO_METADATA_TYPE_LANGUAGE: metadata->language_code = little_endian_read_24(buffer, offset+1); metadata->metadata_mask |= (1 << ltv_type); break; case LE_AUDIO_METADATA_TYPE_CCID_LIST: if (ltv_len > 1){ metadata->ccids_num = btstack_min(ltv_len - 1, LE_CCIDS_MAX_NUM); memcpy(metadata->ccids, &buffer[offset+1], metadata->ccids_num); metadata->metadata_mask |= (1 << ltv_type); } break; case LE_AUDIO_METADATA_TYPE_PARENTAL_RATING: parental_rating = (le_audio_parental_rating_t)buffer[offset+1]; metadata->parental_rating = parental_rating; metadata->metadata_mask |= (1 << ltv_type); break; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI: metadata->program_info_uri_length = btstack_min(ltv_len, LE_AUDIO_PROGRAM_INFO_URI_MAX_LENGTH); memcpy(metadata->program_info_uri, &buffer[offset+1], metadata->program_info_uri_length); metadata->metadata_mask |= (1 << ltv_type); break; case LE_AUDIO_METADATA_TYPE_EXTENDED_METADATA: if (ltv_len < 2){ break; } metadata->extended_metadata_length = btstack_min(ltv_len - 2, LE_AUDIO_EXTENDED_METADATA_MAX_LENGHT); metadata->extended_metadata_type = little_endian_read_16(buffer, offset+1); memcpy(metadata->extended_metadata, &buffer[offset+3], metadata->extended_metadata_length); metadata->metadata_mask |= (1 << LE_AUDIO_METADATA_TYPE_MAPPED_EXTENDED_METADATA_BIT_POSITION); break; case LE_AUDIO_METADATA_TYPE_VENDOR_SPECIFIC_METADATA: if (ltv_len < 2){ break; } metadata->vendor_specific_metadata_length = btstack_min(ltv_len - 2, LE_AUDIO_VENDOR_SPECIFIC_METADATA_MAX_LENGTH); metadata->vendor_specific_company_id = little_endian_read_16(buffer, offset+1); memcpy(metadata->vendor_specific_metadata, &buffer[offset+3], metadata->vendor_specific_metadata_length); metadata->metadata_mask |= (1 << LE_AUDIO_METADATA_TYPE_MAPPED_VENDOR_SPECIFIC_METADATA_BIT_POSITION); break; default: metadata->metadata_mask |= (1 << LE_AUDIO_METADATA_TYPE_RFU); metadata->unsupported_type = ltv_type; break; } offset += ltv_len; } return offset; } uint16_t le_audio_util_metadata_serialize(const le_audio_metadata_t *metadata, uint8_t * event, uint16_t event_size){ UNUSED(event_size); uint8_t pos = 0; event[pos++] = (uint8_t)metadata->metadata_mask; little_endian_store_16(event, pos, metadata->preferred_audio_contexts_mask); pos += 2; little_endian_store_16(event, pos, metadata->streaming_audio_contexts_mask); pos += 2; event[pos++] = metadata->program_info_length; memcpy(&event[pos], &metadata->program_info[0], metadata->program_info_length); pos += metadata->program_info_length; little_endian_store_24(event, pos, metadata->language_code); pos += 3; event[pos++] = metadata->ccids_num; memcpy(&event[pos], &metadata->ccids[0], metadata->ccids_num); pos += metadata->ccids_num; event[pos++] = (uint8_t)metadata->parental_rating; event[pos++] = metadata->program_info_uri_length; memcpy(&event[pos], &metadata->program_info_uri[0], metadata->program_info_uri_length); pos += metadata->program_info_uri_length; little_endian_store_16(event, pos, metadata->extended_metadata_type); pos += 2; event[pos++] = metadata->extended_metadata_length; memcpy(&event[pos], &metadata->extended_metadata[0], metadata->extended_metadata_length); pos += metadata->extended_metadata_length; little_endian_store_16(event, pos, metadata->vendor_specific_company_id); pos += 2; event[pos++] = metadata->vendor_specific_metadata_length; memcpy(&event[pos], &metadata->vendor_specific_metadata[0], metadata->vendor_specific_metadata_length); pos += metadata->vendor_specific_metadata_length; return pos; } static uint16_t le_audio_util_get_value_size_for_metadata_type(const le_audio_metadata_t *metadata, le_audio_metadata_type_t metadata_type){ switch (metadata_type){ case LE_AUDIO_METADATA_TYPE_PREFERRED_AUDIO_CONTEXTS: case LE_AUDIO_METADATA_TYPE_STREAMING_AUDIO_CONTEXTS: return 2; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO: return metadata->program_info_length; case LE_AUDIO_METADATA_TYPE_LANGUAGE: return 3; case LE_AUDIO_METADATA_TYPE_CCID_LIST: return metadata->ccids_num; case LE_AUDIO_METADATA_TYPE_PARENTAL_RATING: return 1; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI: return metadata->program_info_uri_length; case LE_AUDIO_METADATA_TYPE_MAPPED_EXTENDED_METADATA_BIT_POSITION: return 2 + metadata->extended_metadata_length; case LE_AUDIO_METADATA_TYPE_MAPPED_VENDOR_SPECIFIC_METADATA_BIT_POSITION: return 2 + metadata->vendor_specific_metadata_length; default: break; } return 0; } uint16_t le_audio_util_metadata_serialize_using_mask(const le_audio_metadata_t *metadata, uint8_t * tlv_buffer, uint16_t tlv_buffer_size){ uint16_t metadata_type; uint16_t pos = 0; uint16_t remaining_bytes = tlv_buffer_size; for (metadata_type = (uint16_t)LE_AUDIO_METADATA_TYPE_PREFERRED_AUDIO_CONTEXTS; metadata_type < (uint16_t) LE_AUDIO_METADATA_TYPE_RFU; metadata_type++){ if ((metadata->metadata_mask & (1 << metadata_type)) == 0){ continue; } uint8_t payload_length = le_audio_util_get_value_size_for_metadata_type(metadata, (le_audio_metadata_type_t)metadata_type); // ensure that there is enough space in TLV to store length (1), type(1) and payload if (remaining_bytes < (2 + payload_length)){ return pos; } tlv_buffer[pos++] = 1 + payload_length; // add one extra byte to count size of type (1 byte) tlv_buffer[pos++] = metadata_type; switch ((le_audio_metadata_type_t)metadata_type){ case LE_AUDIO_METADATA_TYPE_PREFERRED_AUDIO_CONTEXTS: little_endian_store_16(tlv_buffer, pos, metadata->preferred_audio_contexts_mask); break; case LE_AUDIO_METADATA_TYPE_STREAMING_AUDIO_CONTEXTS: little_endian_store_16(tlv_buffer, pos, metadata->streaming_audio_contexts_mask); break; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO: memcpy(&tlv_buffer[pos], metadata->program_info, metadata->program_info_length); break; case LE_AUDIO_METADATA_TYPE_LANGUAGE: little_endian_store_24(tlv_buffer, pos, metadata->language_code); break; case LE_AUDIO_METADATA_TYPE_CCID_LIST: memcpy(&tlv_buffer[pos], metadata->ccids, metadata->ccids_num); break; case LE_AUDIO_METADATA_TYPE_PARENTAL_RATING: break; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI: memcpy(&tlv_buffer[pos], metadata->program_info_uri, metadata->program_info_uri_length); break; case LE_AUDIO_METADATA_TYPE_MAPPED_EXTENDED_METADATA_BIT_POSITION: little_endian_store_16(tlv_buffer, pos, metadata->extended_metadata_type); memcpy(&tlv_buffer[pos + 2], metadata->extended_metadata, metadata->extended_metadata_length); break; case LE_AUDIO_METADATA_TYPE_MAPPED_VENDOR_SPECIFIC_METADATA_BIT_POSITION: little_endian_store_16(tlv_buffer, pos, metadata->vendor_specific_company_id); memcpy(&tlv_buffer[pos + 2], metadata->vendor_specific_metadata, metadata->vendor_specific_metadata_length); break; default: break; } pos += payload_length; remaining_bytes -= (payload_length + 2); } return pos; } btstack_lc3_frame_duration_t le_audio_util_get_btstack_lc3_frame_duration(le_audio_codec_frame_duration_index_t frame_duration_index){ switch (frame_duration_index){ case LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US: return BTSTACK_LC3_FRAME_DURATION_7500US; case LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US: return BTSTACK_LC3_FRAME_DURATION_10000US; default: btstack_assert(false); break; } return 0; } uint16_t le_audio_get_frame_duration_us(le_audio_codec_frame_duration_index_t frame_duration_index){ switch (frame_duration_index){ case LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US: return 7500; case LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US: return 10000; default: return 0; } } le_audio_codec_frame_duration_index_t le_audio_get_frame_duration_index(uint16_t frame_duration_us){ switch (frame_duration_us){ case 7500: return LE_AUDIO_CODEC_FRAME_DURATION_INDEX_7500US; case 10000: return LE_AUDIO_CODEC_FRAME_DURATION_INDEX_10000US; default: return LE_AUDIO_CODEC_FRAME_DURATION_INDEX_RFU; } } uint32_t le_audio_get_sampling_frequency_hz(le_audio_codec_sampling_frequency_index_t sampling_frequency_index){ switch (sampling_frequency_index){ case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_8000_HZ: return 8000; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_11025_HZ: return 11025; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_16000_HZ: return 16000; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_22050_HZ: return 22050; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_24000_HZ: return 24000; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_32000_HZ: return 32000; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_44100_HZ: return 44100; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_48000_HZ: return 48000; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_88200_HZ: return 88200; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_96000_HZ: return 96000; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_176400_HZ: return 176400; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_192000_HZ: return 192000; case LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_384000_HZ: return 384000; default: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_INVALID; } } le_audio_codec_sampling_frequency_index_t le_audio_get_sampling_frequency_index(uint32_t sampling_frequency_hz){ switch (sampling_frequency_hz){ case 0: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_INVALID; case 8000: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_8000_HZ; case 11025: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_11025_HZ; case 16000: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_16000_HZ; case 22050: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_22050_HZ; case 24000: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_24000_HZ; case 32000: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_32000_HZ; case 44100: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_44100_HZ; case 48000: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_48000_HZ; case 88200: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_88200_HZ; case 96000: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_96000_HZ; case 176400: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_176400_HZ; case 192000: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_192000_HZ; case 384000: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_384000_HZ; default: return LE_AUDIO_CODEC_SAMPLING_FREQUENCY_INDEX_RFU; } } void le_audio_util_metadata_using_mask_from_metadata_event(const uint8_t * packet, uint16_t packet_size, le_audio_metadata_t * metadata_out){ UNUSED(packet_size); btstack_assert(metadata_out != NULL); if (packet_size == 0){ return; } memset(metadata_out, 0, sizeof(le_audio_metadata_t)); metadata_out->metadata_mask = leaudio_subevent_ascs_server_metadata_get_metadata_mask(packet); uint16_t metadata_type; for (metadata_type = (uint16_t)LE_AUDIO_METADATA_TYPE_PREFERRED_AUDIO_CONTEXTS; metadata_type < (uint16_t) LE_AUDIO_METADATA_TYPE_RFU; metadata_type++){ if ((metadata_out->metadata_mask & (1 << metadata_type) ) != 0 ){ switch ((le_audio_metadata_type_t)metadata_type){ case LE_AUDIO_METADATA_TYPE_PREFERRED_AUDIO_CONTEXTS: metadata_out->preferred_audio_contexts_mask = leaudio_subevent_ascs_server_metadata_get_preferred_audio_contexts_mask(packet); break; case LE_AUDIO_METADATA_TYPE_STREAMING_AUDIO_CONTEXTS: metadata_out->streaming_audio_contexts_mask = leaudio_subevent_ascs_server_metadata_get_streaming_audio_contexts_mask(packet); break; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO: metadata_out->program_info_length = leaudio_subevent_ascs_server_metadata_get_program_info_length(packet); memcpy(metadata_out->program_info, leaudio_subevent_ascs_server_metadata_get_program_info(packet), metadata_out->program_info_length); break; case LE_AUDIO_METADATA_TYPE_LANGUAGE: metadata_out->language_code = leaudio_subevent_ascs_server_metadata_get_language_code(packet); break; case LE_AUDIO_METADATA_TYPE_CCID_LIST: metadata_out->ccids_num = leaudio_subevent_ascs_server_metadata_get_ccids_num(packet); memcpy(metadata_out->ccids, leaudio_subevent_ascs_server_metadata_get_ccids(packet), metadata_out->ccids_num); break; case LE_AUDIO_METADATA_TYPE_PARENTAL_RATING: metadata_out->parental_rating = (le_audio_parental_rating_t)leaudio_subevent_ascs_server_metadata_get_parental_rating(packet); break; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI: metadata_out->program_info_uri_length = leaudio_subevent_ascs_server_metadata_get_program_info_uri_length(packet); memcpy(metadata_out->program_info_uri, leaudio_subevent_ascs_server_metadata_get_program_info_uri(packet), metadata_out->program_info_uri_length); break; case LE_AUDIO_METADATA_TYPE_MAPPED_EXTENDED_METADATA_BIT_POSITION: metadata_out->extended_metadata_type = leaudio_subevent_ascs_server_metadata_get_extended_metadata_type(packet); metadata_out->extended_metadata_length = leaudio_subevent_ascs_server_metadata_get_extended_metadata_value_length(packet); memcpy(metadata_out->extended_metadata, leaudio_subevent_ascs_server_metadata_get_extended_metadata_value(packet), metadata_out->extended_metadata_length); break; case LE_AUDIO_METADATA_TYPE_MAPPED_VENDOR_SPECIFIC_METADATA_BIT_POSITION: metadata_out->vendor_specific_company_id = leaudio_subevent_ascs_server_metadata_get_vendor_specific_metadata_type(packet); metadata_out->vendor_specific_metadata_length = leaudio_subevent_ascs_server_metadata_get_vendor_specific_metadata_value_length(packet); memcpy(metadata_out->vendor_specific_metadata, leaudio_subevent_ascs_server_metadata_get_vendor_specific_metadata_value(packet), metadata_out->vendor_specific_metadata_length); break; default: btstack_assert(false); break; } } } } void le_audio_util_metadata_using_mask_from_enable_event(const uint8_t * packet, uint16_t packet_size, le_audio_metadata_t * metadata_out){ UNUSED(packet_size); btstack_assert(metadata_out != NULL); if (packet_size == 0){ return; } memset(metadata_out, 0, sizeof(le_audio_metadata_t)); metadata_out->metadata_mask = leaudio_subevent_ascs_server_metadata_get_metadata_mask(packet); uint16_t metadata_type; for (metadata_type = (uint16_t)LE_AUDIO_METADATA_TYPE_PREFERRED_AUDIO_CONTEXTS; metadata_type < (uint16_t) LE_AUDIO_METADATA_TYPE_RFU; metadata_type++){ if ((metadata_out->metadata_mask & (1 << metadata_type) ) != 0 ){ switch ((le_audio_metadata_type_t)metadata_type){ case LE_AUDIO_METADATA_TYPE_PREFERRED_AUDIO_CONTEXTS: metadata_out->preferred_audio_contexts_mask = leaudio_subevent_ascs_server_enable_get_preferred_audio_contexts_mask(packet); break; case LE_AUDIO_METADATA_TYPE_STREAMING_AUDIO_CONTEXTS: metadata_out->streaming_audio_contexts_mask = leaudio_subevent_ascs_server_enable_get_streaming_audio_contexts_mask(packet); break; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO: metadata_out->program_info_length = leaudio_subevent_ascs_server_enable_get_program_info_length(packet); memcpy(metadata_out->program_info, leaudio_subevent_ascs_server_enable_get_program_info(packet), metadata_out->program_info_length); break; case LE_AUDIO_METADATA_TYPE_LANGUAGE: metadata_out->language_code = leaudio_subevent_ascs_server_enable_get_language_code(packet); break; case LE_AUDIO_METADATA_TYPE_CCID_LIST: metadata_out->ccids_num = leaudio_subevent_ascs_server_enable_get_ccids_num(packet); memcpy(metadata_out->ccids, leaudio_subevent_ascs_server_enable_get_ccids(packet), metadata_out->ccids_num); break; case LE_AUDIO_METADATA_TYPE_PARENTAL_RATING: metadata_out->parental_rating = (le_audio_parental_rating_t)leaudio_subevent_ascs_server_enable_get_parental_rating(packet); break; case LE_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI: metadata_out->program_info_uri_length = leaudio_subevent_ascs_server_enable_get_program_info_uri_length(packet); memcpy(metadata_out->program_info_uri, leaudio_subevent_ascs_server_enable_get_program_info_uri(packet), metadata_out->program_info_uri_length); break; case LE_AUDIO_METADATA_TYPE_MAPPED_EXTENDED_METADATA_BIT_POSITION: metadata_out->extended_metadata_type = leaudio_subevent_ascs_server_enable_get_extended_metadata_type(packet); metadata_out->extended_metadata_length = leaudio_subevent_ascs_server_enable_get_extended_metadata_value_length(packet); memcpy(metadata_out->extended_metadata, leaudio_subevent_ascs_server_enable_get_extended_metadata_value(packet), metadata_out->extended_metadata_length); break; case LE_AUDIO_METADATA_TYPE_MAPPED_VENDOR_SPECIFIC_METADATA_BIT_POSITION: metadata_out->vendor_specific_company_id = leaudio_subevent_ascs_server_enable_get_vendor_specific_metadata_type(packet); metadata_out->vendor_specific_metadata_length = leaudio_subevent_ascs_server_enable_get_vendor_specific_metadata_value_length(packet); memcpy(metadata_out->vendor_specific_metadata, leaudio_subevent_ascs_server_enable_get_vendor_specific_metadata_value(packet), metadata_out->vendor_specific_metadata_length); break; default: btstack_assert(false); break; } } } }