mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-12-16 23:45:37 +08:00
921894c4e2
CC android/android_audio_a2dp_default_la-hal-audio-sbc.lo android/hal-audio-sbc.c: In function 'sbc_codec_init': android/hal-audio-sbc.c:269:2: error: implicit declaration of function 'calloc' [-Werror=implicit-function-declaration] sbc_data = calloc(sizeof(struct sbc_data), 1); ^ android/hal-audio-sbc.c:269:13: error: incompatible implicit declaration of built-in function 'calloc' [-Werror] sbc_data = calloc(sizeof(struct sbc_data), 1); ^ android/hal-audio-sbc.c: In function 'sbc_cleanup': android/hal-audio-sbc.c:291:2: error: implicit declaration of function 'free' [-Werror=implicit-function-declaration] free(codec_data); ^ android/hal-audio-sbc.c:291:2: error: incompatible implicit declaration of built-in function 'free' [-Werror] cc1: all warnings being treated as errors
429 lines
9.5 KiB
C
429 lines
9.5 KiB
C
/*
|
|
* Copyright (C) 2013 Intel Corporation
|
|
*
|
|
* 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 <stdbool.h>
|
|
#include <string.h>
|
|
#include <malloc.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <sbc/sbc.h>
|
|
#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 = MIN_BITPOOL,
|
|
.max_bitpool = MAX_BITPOOL
|
|
},
|
|
{
|
|
.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 = MIN_BITPOOL,
|
|
.max_bitpool = MAX_BITPOOL
|
|
},
|
|
{
|
|
.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 = MIN_BITPOOL,
|
|
.max_bitpool = MAX_BITPOOL
|
|
},
|
|
};
|
|
|
|
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;
|
|
}
|