blob: 7ad3a6a51a0b8bf3fc97086a9fe7560021b40b38 [file] [log] [blame]
/*
* 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;
}