// SPDX-License-Identifier: Apache-2.0 /* * Copyright (C) 2013 Intel Corporation * */ #define _GNU_SOURCE #include #include #include #include #include #include "audio-msg.h" #include "hal-audio.h" #include "hal-log.h" #include "../profiles/audio/a2dp-codecs.h" #define MAX_FRAMES_IN_PAYLOAD 15 #define SBC_QUALITY_MIN_BITPOOL 33 #define SBC_QUALITY_STEP 5 #if __BYTE_ORDER == __LITTLE_ENDIAN struct rtp_payload { unsigned frame_count:4; unsigned rfa0:1; unsigned is_last_fragment:1; unsigned is_first_fragment:1; unsigned is_fragmented:1; } __attribute__ ((packed)); #elif __BYTE_ORDER == __BIG_ENDIAN struct rtp_payload { unsigned is_fragmented:1; unsigned is_first_fragment:1; unsigned is_last_fragment:1; unsigned rfa0:1; unsigned frame_count:4; } __attribute__ ((packed)); #else #error "Unknown byte order" #endif struct media_packet_sbc { struct media_packet_rtp hdr; struct rtp_payload payload; uint8_t data[0]; }; struct sbc_data { a2dp_sbc_t sbc; sbc_t enc; uint16_t payload_len; size_t in_frame_len; size_t in_buf_size; size_t out_frame_len; unsigned frame_duration; unsigned frames_per_packet; }; static const a2dp_sbc_t sbc_presets[] = { { .frequency = SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000, .channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO, .subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8, .allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS, .block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16, .min_bitpool = SBC_MIN_BITPOOL, .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_44100, }, { .frequency = SBC_SAMPLING_FREQ_44100, .channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO, .subbands = SBC_SUBBANDS_8, .allocation_method = SBC_ALLOCATION_LOUDNESS, .block_length = SBC_BLOCK_LENGTH_16, .min_bitpool = SBC_MIN_BITPOOL, .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_44100, }, { .frequency = SBC_SAMPLING_FREQ_48000, .channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO, .subbands = SBC_SUBBANDS_8, .allocation_method = SBC_ALLOCATION_LOUDNESS, .block_length = SBC_BLOCK_LENGTH_16, .min_bitpool = SBC_MIN_BITPOOL, .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_48000, }, }; static int sbc_get_presets(struct audio_preset *preset, size_t *len) { int i; int count; size_t new_len = 0; uint8_t *ptr = (uint8_t *) preset; size_t preset_size = sizeof(*preset) + sizeof(a2dp_sbc_t); count = sizeof(sbc_presets) / sizeof(sbc_presets[0]); for (i = 0; i < count; i++) { preset = (struct audio_preset *) ptr; if (new_len + preset_size > *len) break; preset->len = sizeof(a2dp_sbc_t); memcpy(preset->data, &sbc_presets[i], preset->len); new_len += preset_size; ptr += preset_size; } *len = new_len; return i; } static int sbc_freq2int(uint8_t freq) { switch (freq) { case SBC_SAMPLING_FREQ_16000: return 16000; case SBC_SAMPLING_FREQ_32000: return 32000; case SBC_SAMPLING_FREQ_44100: return 44100; case SBC_SAMPLING_FREQ_48000: return 48000; default: return 0; } } static const char *sbc_mode2str(uint8_t mode) { switch (mode) { case SBC_CHANNEL_MODE_MONO: return "Mono"; case SBC_CHANNEL_MODE_DUAL_CHANNEL: return "DualChannel"; case SBC_CHANNEL_MODE_STEREO: return "Stereo"; case SBC_CHANNEL_MODE_JOINT_STEREO: return "JointStereo"; default: return "(unknown)"; } } static int sbc_blocks2int(uint8_t blocks) { switch (blocks) { case SBC_BLOCK_LENGTH_4: return 4; case SBC_BLOCK_LENGTH_8: return 8; case SBC_BLOCK_LENGTH_12: return 12; case SBC_BLOCK_LENGTH_16: return 16; default: return 0; } } static int sbc_subbands2int(uint8_t subbands) { switch (subbands) { case SBC_SUBBANDS_4: return 4; case SBC_SUBBANDS_8: return 8; default: return 0; } } static const char *sbc_allocation2str(uint8_t allocation) { switch (allocation) { case SBC_ALLOCATION_SNR: return "SNR"; case SBC_ALLOCATION_LOUDNESS: return "Loudness"; default: return "(unknown)"; } } static void sbc_init_encoder(struct sbc_data *sbc_data) { a2dp_sbc_t *in = &sbc_data->sbc; sbc_t *out = &sbc_data->enc; sbc_init_a2dp(out, 0L, in, sizeof(*in)); out->endian = SBC_LE; out->bitpool = in->max_bitpool; DBG("frequency=%d channel_mode=%s block_length=%d subbands=%d allocation=%s bitpool=%d-%d", sbc_freq2int(in->frequency), sbc_mode2str(in->channel_mode), sbc_blocks2int(in->block_length), sbc_subbands2int(in->subbands), sbc_allocation2str(in->allocation_method), in->min_bitpool, in->max_bitpool); } static void sbc_codec_calculate(struct sbc_data *sbc_data) { size_t in_frame_len; size_t out_frame_len; size_t num_frames; in_frame_len = sbc_get_codesize(&sbc_data->enc); out_frame_len = sbc_get_frame_length(&sbc_data->enc); num_frames = sbc_data->payload_len / out_frame_len; if (num_frames > MAX_FRAMES_IN_PAYLOAD) num_frames = MAX_FRAMES_IN_PAYLOAD; sbc_data->in_frame_len = in_frame_len; sbc_data->in_buf_size = num_frames * in_frame_len; sbc_data->out_frame_len = out_frame_len; sbc_data->frame_duration = sbc_get_frame_duration(&sbc_data->enc); sbc_data->frames_per_packet = num_frames; DBG("in_frame_len=%zu out_frame_len=%zu frames_per_packet=%zu", in_frame_len, out_frame_len, num_frames); } static bool sbc_codec_init(struct audio_preset *preset, uint16_t payload_len, void **codec_data) { struct sbc_data *sbc_data; if (preset->len != sizeof(a2dp_sbc_t)) { error("SBC: preset size mismatch"); return false; } sbc_data = calloc(sizeof(struct sbc_data), 1); if (!sbc_data) return false; memcpy(&sbc_data->sbc, preset->data, preset->len); sbc_init_encoder(sbc_data); sbc_data->payload_len = payload_len; sbc_codec_calculate(sbc_data); *codec_data = sbc_data; return true; } static bool sbc_cleanup(void *codec_data) { struct sbc_data *sbc_data = (struct sbc_data *) codec_data; sbc_finish(&sbc_data->enc); free(codec_data); return true; } static bool sbc_get_config(void *codec_data, struct audio_input_config *config) { struct sbc_data *sbc_data = (struct sbc_data *) codec_data; switch (sbc_data->sbc.frequency) { case SBC_SAMPLING_FREQ_16000: config->rate = 16000; break; case SBC_SAMPLING_FREQ_32000: config->rate = 32000; break; case SBC_SAMPLING_FREQ_44100: config->rate = 44100; break; case SBC_SAMPLING_FREQ_48000: config->rate = 48000; break; default: return false; } config->channels = sbc_data->sbc.channel_mode == SBC_CHANNEL_MODE_MONO ? AUDIO_CHANNEL_OUT_MONO : AUDIO_CHANNEL_OUT_STEREO; config->format = AUDIO_FORMAT_PCM_16_BIT; return true; } static size_t sbc_get_buffer_size(void *codec_data) { struct sbc_data *sbc_data = (struct sbc_data *) codec_data; return sbc_data->in_buf_size; } static size_t sbc_get_mediapacket_duration(void *codec_data) { struct sbc_data *sbc_data = (struct sbc_data *) codec_data; return sbc_data->frame_duration * sbc_data->frames_per_packet; } static ssize_t sbc_encode_mediapacket(void *codec_data, const uint8_t *buffer, size_t len, struct media_packet *mp, size_t mp_data_len, size_t *written) { struct sbc_data *sbc_data = (struct sbc_data *) codec_data; struct media_packet_sbc *mp_sbc = (struct media_packet_sbc *) mp; size_t consumed = 0; size_t encoded = 0; uint8_t frame_count = 0; mp_data_len -= sizeof(mp_sbc->payload); while (len - consumed >= sbc_data->in_frame_len && mp_data_len - encoded >= sbc_data->out_frame_len && frame_count < sbc_data->frames_per_packet) { ssize_t read; ssize_t written = 0; read = sbc_encode(&sbc_data->enc, buffer + consumed, sbc_data->in_frame_len, mp_sbc->data + encoded, mp_data_len - encoded, &written); if (read < 0) { error("SBC: failed to encode block at frame %d (%zd)", frame_count, read); break; } frame_count++; consumed += read; encoded += written; } *written = encoded + sizeof(mp_sbc->payload); mp_sbc->payload.frame_count = frame_count; return consumed; } static bool sbc_update_qos(void *codec_data, uint8_t op) { struct sbc_data *sbc_data = (struct sbc_data *) codec_data; uint8_t curr_bitpool = sbc_data->enc.bitpool; uint8_t new_bitpool = curr_bitpool; switch (op) { case QOS_POLICY_DEFAULT: new_bitpool = sbc_data->sbc.max_bitpool; break; case QOS_POLICY_DECREASE: if (curr_bitpool > SBC_QUALITY_MIN_BITPOOL) { new_bitpool = curr_bitpool - SBC_QUALITY_STEP; if (new_bitpool < SBC_QUALITY_MIN_BITPOOL) new_bitpool = SBC_QUALITY_MIN_BITPOOL; } break; } if (new_bitpool == curr_bitpool) return false; sbc_data->enc.bitpool = new_bitpool; sbc_codec_calculate(sbc_data); info("SBC: bitpool changed: %d -> %d", curr_bitpool, new_bitpool); return true; } static const struct audio_codec codec = { .type = A2DP_CODEC_SBC, .use_rtp = true, .get_presets = sbc_get_presets, .init = sbc_codec_init, .cleanup = sbc_cleanup, .get_config = sbc_get_config, .get_buffer_size = sbc_get_buffer_size, .get_mediapacket_duration = sbc_get_mediapacket_duration, .encode_mediapacket = sbc_encode_mediapacket, .update_qos = sbc_update_qos, }; const struct audio_codec *codec_sbc(void) { return &codec; }