blob: 6f12edd5e8df5f7becda5f691a305851704e945f [file] [log] [blame]
/*
* Copyright 2021 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 <errno.h>
#include <grp.h>
#include <math.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cfloat>
#include <memory>
// Define before including log.h
#define LOG_TAG "sco_hci"
#include "btif/include/core_callbacks.h"
#include "btif/include/stack_manager.h"
#include "osi/include/allocator.h"
#include "osi/include/log.h"
#include "stack/btm/btm_sco.h"
#include "udrv/include/uipc.h"
#define SCO_DATA_READ_POLL_MS 10
#define SCO_HOST_DATA_PATH "/var/run/bluetooth/audio/.sco_data"
// TODO(b/198260375): Make SCO data owner group configurable.
#define SCO_HOST_DATA_GROUP "bluetooth-audio"
/* Per Bluetooth Core v5.0 and HFP 1.9 specification. */
#define BTM_MSBC_H2_HEADER_0 0x01
#define BTM_MSBC_H2_HEADER_LEN 2
#define BTM_MSBC_PKT_LEN 60
#define BTM_MSBC_PKT_FRAME_LEN 57 /* Packet length without the header */
#define BTM_MSBC_SYNC_WORD 0xAD
/* Used by PLC */
#define BTM_MSBC_SAMPLE_SIZE 2 /* 2 bytes*/
#define BTM_MSBC_FS 120 /* Frame Size */
#define BTM_PLC_WL 256 /* 16ms - Window Length for pattern matching */
#define BTM_PLC_TL 64 /* 4ms - Template Length for matching */
#define BTM_PLC_HL \
(BTM_PLC_WL + BTM_MSBC_FS - 1) /* Length of History buffer required */
#define BTM_PLC_SBCRL 36 /* SBC Reconvergence sample Length */
#define BTM_PLC_OLAL 16 /* OverLap-Add Length */
/* Disable the PLC when there are more than threshold of lost packets in the
* window */
#define BTM_PLC_WINDOW_SIZE 5
#define BTM_PLC_PL_THRESHOLD 1
/* LC3 definitions */
#define BTM_LC3_H2_HEADER_0 0x01
#define BTM_LC3_H2_HEADER_LEN 2
#define BTM_LC3_PKT_LEN 60
#define BTM_LC3_FS 240 /* Frame Size */
namespace {
std::unique_ptr<tUIPC_STATE> sco_uipc = nullptr;
void sco_data_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
switch (event) {
case UIPC_OPEN_EVT:
/*
* Read directly from media task from here on (keep callback for
* connection events.
*/
UIPC_Ioctl(*sco_uipc, UIPC_CH_ID_AV_AUDIO, UIPC_REG_REMOVE_ACTIVE_READSET,
NULL);
UIPC_Ioctl(*sco_uipc, UIPC_CH_ID_AV_AUDIO, UIPC_SET_READ_POLL_TMO,
reinterpret_cast<void*>(SCO_DATA_READ_POLL_MS));
break;
default:
break;
}
}
} // namespace
namespace bluetooth {
namespace audio {
namespace sco {
void open() {
if (sco_uipc != nullptr) {
LOG_WARN("Re-opening UIPC that is already running");
}
sco_uipc = UIPC_Init();
if (sco_uipc == nullptr) {
LOG_ERROR("%s failed to init UIPC", __func__);
return;
}
UIPC_Open(*sco_uipc, UIPC_CH_ID_AV_AUDIO, sco_data_cb, SCO_HOST_DATA_PATH);
struct group* grp = getgrnam(SCO_HOST_DATA_GROUP);
chmod(SCO_HOST_DATA_PATH, 0770);
if (grp) {
int res = chown(SCO_HOST_DATA_PATH, -1, grp->gr_gid);
if (res == -1) {
LOG_ERROR("%s failed: %s", __func__, strerror(errno));
}
}
}
void cleanup() {
if (sco_uipc == nullptr) {
return;
}
UIPC_Close(*sco_uipc, UIPC_CH_ID_ALL);
sco_uipc = nullptr;
}
size_t read(uint8_t* p_buf, uint32_t len) {
if (sco_uipc == nullptr) {
LOG_WARN("Read from uninitialized or closed UIPC");
return 0;
}
return UIPC_Read(*sco_uipc, UIPC_CH_ID_AV_AUDIO, p_buf, len);
}
size_t write(const uint8_t* p_buf, uint32_t len) {
if (sco_uipc == nullptr) {
LOG_WARN("Write to uninitialized or closed UIPC");
return 0;
}
return UIPC_Send(*sco_uipc, UIPC_CH_ID_AV_AUDIO, 0, p_buf, len) ? len : 0;
}
namespace wbs {
/* Second octet of H2 header is composed by 4 bits fixed 0x8 and 4 bits
* sequence number 0000, 0011, 1100, 1111. */
static const uint8_t btm_h2_header_frames_count[] = {0x08, 0x38, 0xc8, 0xf8};
/* Supported SCO packet sizes for mSBC. The wideband speech mSBC frame parsing
* code ties to limited packet size values. Specifically list them out
* to check against when setting packet size. The first entry is the default
* value as a fallback. */
constexpr size_t btm_wbs_supported_pkt_size[] = {BTM_MSBC_PKT_LEN, 72, 0};
/* Buffer size should be set to least common multiple of SCO packet size and
* BTM_MSBC_PKT_LEN for optimizing buffer copy. */
constexpr size_t btm_wbs_msbc_buffer_size[] = {BTM_MSBC_PKT_LEN, 360, 0};
/* The pre-computed SCO packet per HFP 1.7 spec. This mSBC packet will be
* decoded into all-zero input PCM. */
static const uint8_t btm_msbc_zero_packet[] = {
0x01, 0x08, /* Mock H2 header */
0xad, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x77, 0x6d, 0xb6, 0xdd,
0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77, 0x6d, 0xb6,
0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77, 0x6d,
0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77,
0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6c,
/* End of Audio Samples */
0x00 /* A padding byte defined by mSBC */};
/* Raised Cosine table for OLA */
static const float rcos[BTM_PLC_OLAL] = {
0.99148655f, 0.96623611f, 0.92510857f, 0.86950446f,
0.80131732f, 0.72286918f, 0.63683150f, 0.54613418f,
0.45386582f, 0.36316850f, 0.27713082f, 0.19868268f,
0.13049554f, 0.07489143f, 0.03376389f, 0.00851345f};
static int16_t f_to_s16(float input) {
return input > INT16_MAX ? INT16_MAX
: input < INT16_MIN ? INT16_MIN
: (int16_t)input;
}
/* This structure tracks the packet loss for last PLC_WINDOW_SIZE of packets */
struct tBTM_MSBC_BTM_PLC_WINDOW {
bool loss_hist[BTM_PLC_WINDOW_SIZE]; /* The packet loss history of receiving
packets.*/
unsigned int idx; /* The index of the to be updated packet loss status. */
unsigned int count; /* The count of lost packets in the window. */
public:
void update_plc_state(bool is_packet_loss) {
bool* curr = &loss_hist[idx];
if (is_packet_loss != *curr) {
count += (is_packet_loss - *curr);
*curr = is_packet_loss;
}
idx = (idx + 1) % BTM_PLC_WINDOW_SIZE;
}
bool is_packet_loss_too_high() {
/* The packet loss count comes from a time window and we use it as an
* indicator of our confidence of the PLC algorithm. It is known to
* generate poorer and robotic feeling sounds, when the majority of
* samples in the PLC history buffer are from the concealment results.
*/
return count > BTM_PLC_PL_THRESHOLD;
}
};
/* The PLC is specifically designed for mSBC. The algorithm searches the
* history of receiving samples to find the best match samples and constructs
* substitutions for the lost samples. The selection is based on pattern
* matching a template, composed of a length of samples preceding to the lost
* samples. It then uses the following samples after the best match as the
* replacement samples and applies Overlap-Add to reduce the audible
* distortion.
*
* This structure holds related info needed to conduct the PLC algorithm.
*/
struct tBTM_MSBC_PLC {
int16_t hist[BTM_PLC_HL + BTM_MSBC_FS + BTM_PLC_SBCRL +
BTM_PLC_OLAL]; /* The history buffer for receiving samples, we
also use it to buffer the processed
replacement samples */
unsigned best_lag; /* The index of the best substitution samples in the
sample history */
int handled_bad_frames; /* Number of bad frames handled since the last good
frame */
int16_t decoded_buffer[BTM_MSBC_FS]; /* Used for storing the samples from
decoding the mSBC zero frame packet and
also constructed frames */
tBTM_MSBC_BTM_PLC_WINDOW*
pl_window; /* Used to monitor how many packets are bad within the recent
BTM_PLC_WINDOW_SIZE of packets. We use this to determine if
we want to disable the PLC temporarily */
int num_decoded_frames; /* Number of total read mSBC frames. */
int num_lost_frames; /* Number of total lost mSBC frames. */
void overlap_add(int16_t* output, float scaler_d, const int16_t* desc,
float scaler_a, const int16_t* asc) {
for (int i = 0; i < BTM_PLC_OLAL; i++) {
output[i] = f_to_s16(scaler_d * desc[i] * rcos[i] +
scaler_a * asc[i] * rcos[BTM_PLC_OLAL - 1 - i]);
}
}
float cross_correlation(int16_t* x, int16_t* y) {
float sum = 0, x2 = 0, y2 = 0;
for (int i = 0; i < BTM_PLC_TL; i++) {
sum += ((float)x[i]) * y[i];
x2 += ((float)x[i]) * x[i];
y2 += ((float)y[i]) * y[i];
}
return sum / sqrtf(x2 * y2);
}
int pattern_match(int16_t* hist) {
int best = 0;
float cn, max_cn = FLT_MIN;
for (int i = 0; i < BTM_PLC_WL; i++) {
cn = cross_correlation(&hist[BTM_PLC_HL - BTM_PLC_TL], &hist[i]);
if (cn > max_cn) {
best = i;
max_cn = cn;
}
}
return best;
}
float amplitude_match(int16_t* x, int16_t* y) {
uint32_t sum_x = 0, sum_y = 0;
float scaler;
for (int i = 0; i < BTM_MSBC_FS; i++) {
sum_x += abs(x[i]);
sum_y += abs(y[i]);
}
if (sum_y == 0) return 1.2f;
scaler = (float)sum_x / sum_y;
return scaler > 1.2f ? 1.2f : scaler < 0.75f ? 0.75f : scaler;
}
public:
void init() {
if (pl_window) osi_free(pl_window);
pl_window = (tBTM_MSBC_BTM_PLC_WINDOW*)osi_calloc(sizeof(*pl_window));
}
void deinit() {
if (pl_window) osi_free_and_reset((void**)&pl_window);
}
int get_num_decoded_frames() { return num_decoded_frames; }
int get_num_lost_frames() { return num_lost_frames; }
void handle_bad_frames(const uint8_t** output) {
float scaler;
int16_t* best_match_hist;
int16_t* frame_head = &hist[BTM_PLC_HL];
num_decoded_frames++;
num_lost_frames++;
/* mSBC codec is stateful, the history of signal would contribute to the
* decode result decoded_buffer. This should never fail. */
GetInterfaceToProfiles()->msbcCodec->decodePacket(
btm_msbc_zero_packet, decoded_buffer, sizeof(decoded_buffer));
/* The PLC algorithm is more likely to generate bad results that sound
* robotic after severe packet losses happened. Only applying it when
* we are confident. */
if (!pl_window->is_packet_loss_too_high()) {
if (handled_bad_frames == 0) {
/* Finds the best matching samples and amplitude */
best_lag = pattern_match(hist) + BTM_PLC_TL;
best_match_hist = &hist[best_lag];
scaler =
amplitude_match(&hist[BTM_PLC_HL - BTM_MSBC_FS], best_match_hist);
/* Constructs the substitution samples */
overlap_add(frame_head, 1.0, decoded_buffer, scaler, best_match_hist);
for (int i = BTM_PLC_OLAL; i < BTM_MSBC_FS; i++)
hist[BTM_PLC_HL + i] = f_to_s16(scaler * best_match_hist[i]);
overlap_add(&frame_head[BTM_MSBC_FS], scaler,
&best_match_hist[BTM_MSBC_FS], 1.0,
&best_match_hist[BTM_MSBC_FS]);
memmove(&frame_head[BTM_MSBC_FS + BTM_PLC_OLAL],
&best_match_hist[BTM_MSBC_FS + BTM_PLC_OLAL],
BTM_PLC_SBCRL * BTM_MSBC_SAMPLE_SIZE);
} else {
/* Using the existing best lag and copy the following frames */
memmove(frame_head, &hist[best_lag],
(BTM_MSBC_FS + BTM_PLC_SBCRL + BTM_PLC_OLAL) *
BTM_MSBC_SAMPLE_SIZE);
}
/* Copy the constructed frames to decoded buffer for caller to use */
std::copy(frame_head, &frame_head[BTM_MSBC_FS], decoded_buffer);
handled_bad_frames++;
} else {
/* This is a case similar to receiving a good frame with all zeros, we set
* handled_bad_frames to zero to prevent the following good frame from
* being concealed to reconverge with the zero frames we fill in. The
* concealment result sounds more artificial and weird than simply writing
* zeros and following samples.
*/
std::copy(std::begin(decoded_buffer), std::end(decoded_buffer),
frame_head);
std::fill(&frame_head[BTM_MSBC_FS],
&frame_head[BTM_MSBC_FS + BTM_PLC_SBCRL + BTM_PLC_OLAL], 0);
/* No need to copy the frames as we'll use the decoded zero frames in the
* decoded buffer as our concealment frames */
handled_bad_frames = 0;
}
*output = (const uint8_t*)decoded_buffer;
/* Shift the frames to update the history window */
memmove(hist, &hist[BTM_MSBC_FS],
(BTM_PLC_HL + BTM_PLC_SBCRL + BTM_PLC_OLAL) * BTM_MSBC_SAMPLE_SIZE);
pl_window->update_plc_state(1);
}
void handle_good_frames(int16_t* input) {
int16_t* frame_head;
num_decoded_frames++;
if (handled_bad_frames != 0) {
/* If there was a packet concealment before this good frame, we need to
* reconverge the input frames */
frame_head = &hist[BTM_PLC_HL];
/* For the first good frame after packet loss, we need to conceal the
* received samples to have it reconverge with the true output */
std::copy(frame_head, &frame_head[BTM_PLC_SBCRL], input);
/* Overlap the input frame with the previous output frame */
overlap_add(&input[BTM_PLC_SBCRL], 1.0, &frame_head[BTM_PLC_SBCRL], 1.0,
&input[BTM_PLC_SBCRL]);
handled_bad_frames = 0;
}
/* Shift the history and update the good frame to the end of it */
memmove(hist, &hist[BTM_MSBC_FS],
(BTM_PLC_HL - BTM_MSBC_FS) * BTM_MSBC_SAMPLE_SIZE);
std::copy(input, &input[BTM_MSBC_FS], &hist[BTM_PLC_HL - BTM_MSBC_FS]);
pl_window->update_plc_state(0);
}
};
/* Define the structure that contains mSBC data */
struct tBTM_MSBC_INFO {
size_t packet_size; /* SCO mSBC packet size supported by lower layer */
size_t buf_size; /* The size of the buffer, determined by the packet_size. */
uint8_t* msbc_decode_buf; /* Buffer to store mSBC packets to decode */
size_t decode_buf_wo; /* Write offset of the decode buffer */
size_t decode_buf_ro; /* Read offset of the decode buffer */
bool read_corrupted; /* If the current mSBC packet read is corrupted */
uint8_t* msbc_encode_buf; /* Buffer to store the encoded SCO packets */
size_t encode_buf_wo; /* Write offset of the encode buffer */
size_t encode_buf_ro; /* Read offset of the encode buffer */
int16_t decoded_pcm_buf[BTM_MSBC_FS]; /* Buffer to store decoded PCM */
uint8_t num_encoded_msbc_pkts; /* Number of the encoded mSBC packets */
tBTM_MSBC_PLC* plc; /* PLC component to handle the packet loss of input */
tBTM_SCO_PKT_STATUS* pkt_status; /* Record of mSBC packet status */
static size_t get_supported_packet_size(size_t pkt_size,
size_t* buffer_size) {
int i;
for (i = 0; btm_wbs_supported_pkt_size[i] != 0 &&
btm_wbs_supported_pkt_size[i] != pkt_size;
i++)
;
/* In case of unsupported value, error log and fallback to
* BTM_MSBC_PKT_LEN(60). */
if (btm_wbs_supported_pkt_size[i] == 0) {
LOG_WARN("Unsupported packet size %lu", (unsigned long)pkt_size);
i = 0;
}
if (buffer_size) {
*buffer_size = btm_wbs_msbc_buffer_size[i];
}
return btm_wbs_supported_pkt_size[i];
}
bool verify_h2_header_seq_num(const uint8_t num) {
for (int i = 0; i < 4; i++) {
if (num == btm_h2_header_frames_count[i]) {
return true;
}
}
return false;
}
public:
size_t init(size_t pkt_size) {
decode_buf_wo = 0;
decode_buf_ro = 0;
encode_buf_wo = 0;
encode_buf_ro = 0;
pkt_size = get_supported_packet_size(pkt_size, &buf_size);
if (pkt_size == packet_size) return packet_size;
packet_size = pkt_size;
if (msbc_decode_buf) osi_free(msbc_decode_buf);
msbc_decode_buf = (uint8_t*)osi_calloc(buf_size);
if (msbc_encode_buf) osi_free(msbc_encode_buf);
msbc_encode_buf = (uint8_t*)osi_calloc(buf_size);
if (plc) {
plc->deinit();
osi_free(plc);
}
plc = (tBTM_MSBC_PLC*)osi_calloc(sizeof(*plc));
plc->init();
if (pkt_status) osi_free(pkt_status);
pkt_status = (tBTM_SCO_PKT_STATUS*)osi_calloc(sizeof(*pkt_status));
pkt_status->init();
return packet_size;
}
void deinit() {
if (msbc_decode_buf) osi_free(msbc_decode_buf);
if (msbc_encode_buf) osi_free(msbc_encode_buf);
if (plc) {
plc->deinit();
osi_free_and_reset((void**)&plc);
}
if (pkt_status) osi_free_and_reset((void**)&pkt_status);
}
size_t decodable() { return decode_buf_wo - decode_buf_ro; }
void mark_pkt_decoded() {
if (decode_buf_ro + BTM_MSBC_PKT_LEN > decode_buf_wo) {
LOG_ERROR("Trying to mark read offset beyond write offset.");
return;
}
decode_buf_ro += BTM_MSBC_PKT_LEN;
if (decode_buf_ro == decode_buf_wo) {
decode_buf_ro = 0;
decode_buf_wo = 0;
}
}
size_t write(const std::vector<uint8_t>& input) {
if (input.size() > buf_size - decode_buf_wo) {
return 0;
}
std::copy(input.begin(), input.end(), msbc_decode_buf + decode_buf_wo);
decode_buf_wo += input.size();
return input.size();
}
const uint8_t* find_msbc_pkt_head() {
if (read_corrupted) {
LOG_DEBUG("Skip corrupted mSBC packets");
read_corrupted = false;
return nullptr;
}
size_t rp = 0;
while (rp < BTM_MSBC_PKT_LEN &&
decode_buf_wo - (decode_buf_ro + rp) >= BTM_MSBC_PKT_LEN) {
if ((msbc_decode_buf[decode_buf_ro + rp] != BTM_MSBC_H2_HEADER_0) ||
(!verify_h2_header_seq_num(
msbc_decode_buf[decode_buf_ro + rp + 1])) ||
(msbc_decode_buf[decode_buf_ro + rp + 2] != BTM_MSBC_SYNC_WORD)) {
rp++;
continue;
}
if (rp != 0) {
LOG_WARN("Skipped %lu bytes of mSBC data ahead of a valid mSBC frame",
(unsigned long)rp);
decode_buf_ro += rp;
}
return &msbc_decode_buf[decode_buf_ro];
}
return nullptr;
}
/* Fill in the mSBC header and update the buffer's write offset to guard the
* buffer space to be written. Return a pointer to the start of mSBC packet's
* body for the caller to fill the encoded mSBC data if there is enough space
* in the buffer to fill in a new packet, otherwise return a nullptr. */
uint8_t* fill_msbc_pkt_template() {
uint8_t* wp = &msbc_encode_buf[encode_buf_wo];
if (buf_size - encode_buf_wo < BTM_MSBC_PKT_LEN) {
LOG_DEBUG("Packet queue can't accommodate more packets.");
return nullptr;
}
wp[0] = BTM_MSBC_H2_HEADER_0;
wp[1] = btm_h2_header_frames_count[num_encoded_msbc_pkts % 4];
encode_buf_wo += BTM_MSBC_PKT_LEN;
num_encoded_msbc_pkts++;
return wp + BTM_MSBC_H2_HEADER_LEN;
}
size_t mark_pkt_dequeued() {
LOG_DEBUG(
"Try to mark an encoded packet dequeued: ro:%lu wo:%lu pkt_size:%lu",
(unsigned long)encode_buf_ro, (unsigned long)encode_buf_wo,
(unsigned long)packet_size);
if (encode_buf_wo - encode_buf_ro < packet_size) return 0;
encode_buf_ro += packet_size;
if (encode_buf_ro == encode_buf_wo) {
encode_buf_ro = 0;
encode_buf_wo = 0;
}
return packet_size;
}
const uint8_t* sco_pkt_read_ptr() {
if (encode_buf_wo - encode_buf_ro < packet_size) {
LOG_DEBUG("Insufficient data as a SCO packet to read.");
return nullptr;
}
return &msbc_encode_buf[encode_buf_ro];
}
};
static tBTM_MSBC_INFO* msbc_info = nullptr;
size_t init(size_t pkt_size) {
GetInterfaceToProfiles()->msbcCodec->initialize();
if (msbc_info) {
LOG_WARN("Re-initiating mSBC buffer that is active or not cleaned");
msbc_info->deinit();
osi_free(msbc_info);
}
msbc_info = (tBTM_MSBC_INFO*)osi_calloc(sizeof(*msbc_info));
return msbc_info->init(pkt_size);
}
void cleanup() {
GetInterfaceToProfiles()->msbcCodec->cleanup();
if (msbc_info == nullptr) return;
msbc_info->deinit();
osi_free_and_reset((void**)&msbc_info);
}
bool fill_plc_stats(int* num_decoded_frames, double* packet_loss_ratio) {
if (msbc_info == NULL || num_decoded_frames == NULL ||
packet_loss_ratio == NULL)
return false;
int decoded_frames = msbc_info->plc->get_num_decoded_frames();
int lost_frames = msbc_info->plc->get_num_lost_frames();
if (decoded_frames <= 0 || lost_frames < 0 || lost_frames > decoded_frames)
return false;
*num_decoded_frames = decoded_frames;
*packet_loss_ratio = (double)lost_frames / decoded_frames;
return true;
}
bool enqueue_packet(const std::vector<uint8_t>& data, bool corrupted) {
if (msbc_info == nullptr) {
LOG_WARN("mSBC buffer uninitialized or cleaned");
return false;
}
if (data.size() != msbc_info->packet_size) {
LOG_WARN(
"Ignoring the coming packet with size %lu that is inconsistent with "
"the HAL reported packet size %lu",
(unsigned long)data.size(), (unsigned long)msbc_info->packet_size);
return false;
}
msbc_info->read_corrupted |= corrupted;
if (msbc_info->write(data) != data.size()) {
LOG_DEBUG("Fail to write packet with size %lu to buffer",
(unsigned long)data.size());
return false;
}
return true;
}
size_t decode(const uint8_t** out_data) {
const uint8_t* frame_head = nullptr;
if (msbc_info == nullptr) {
LOG_WARN("mSBC buffer uninitialized or cleaned");
return 0;
}
if (out_data == nullptr) {
LOG_WARN("%s Invalid output pointer", __func__);
return 0;
}
if (msbc_info->decodable() < BTM_MSBC_PKT_LEN) {
LOG_DEBUG("No complete mSBC packet to decode");
return 0;
}
frame_head = msbc_info->find_msbc_pkt_head();
if (frame_head == nullptr) {
LOG_DEBUG("No valid mSBC packet to decode %lu, %lu",
(unsigned long)msbc_info->decode_buf_ro,
(unsigned long)msbc_info->decode_buf_wo);
/* Done with parsing the raw bytes just read. If we couldn't find a valid
* mSBC frame head, we shall treat the existing BTM_MSBC_PKT_LEN length
* of mSBC data as a corrupted packet and conduct the PLC. */
goto packet_loss;
}
if (!GetInterfaceToProfiles()->msbcCodec->decodePacket(
frame_head, msbc_info->decoded_pcm_buf,
sizeof(msbc_info->decoded_pcm_buf))) {
LOG_DEBUG("Decoding mSBC packet failed");
goto packet_loss;
}
msbc_info->plc->handle_good_frames(msbc_info->decoded_pcm_buf);
msbc_info->pkt_status->update(false);
*out_data = (const uint8_t*)msbc_info->decoded_pcm_buf;
msbc_info->mark_pkt_decoded();
return BTM_MSBC_CODE_SIZE;
packet_loss:
msbc_info->plc->handle_bad_frames(out_data);
msbc_info->pkt_status->update(true);
msbc_info->mark_pkt_decoded();
return BTM_MSBC_CODE_SIZE;
}
size_t encode(int16_t* data, size_t len) {
uint8_t* pkt_body = nullptr;
uint32_t encoded_size = 0;
if (msbc_info == nullptr) {
LOG_WARN("mSBC buffer uninitialized or cleaned");
return 0;
}
if (data == nullptr) {
LOG_WARN("Invalid data to encode");
return 0;
}
if (len < BTM_MSBC_CODE_SIZE) {
LOG_DEBUG(
"PCM frames with size %lu is insufficient to be encoded into a mSBC "
"packet",
(unsigned long)len);
return 0;
}
pkt_body = msbc_info->fill_msbc_pkt_template();
if (pkt_body == nullptr) {
LOG_DEBUG("Failed to fill the template to fill the mSBC packet");
return 0;
}
encoded_size =
GetInterfaceToProfiles()->msbcCodec->encodePacket(data, pkt_body);
if (encoded_size != BTM_MSBC_PKT_FRAME_LEN) {
LOG_WARN("Encoding invalid packet size: %lu", (unsigned long)encoded_size);
std::copy(&btm_msbc_zero_packet[BTM_MSBC_H2_HEADER_LEN],
std::end(btm_msbc_zero_packet), pkt_body);
}
return BTM_MSBC_CODE_SIZE;
}
size_t dequeue_packet(const uint8_t** output) {
if (msbc_info == nullptr) {
LOG_WARN("mSBC buffer uninitialized or cleaned");
return 0;
}
if (output == nullptr) {
LOG_WARN("%s Invalid output pointer", __func__);
return 0;
}
*output = msbc_info->sco_pkt_read_ptr();
if (*output == nullptr) {
LOG_DEBUG("Insufficient data to dequeue.");
return 0;
}
return msbc_info->mark_pkt_dequeued();
}
tBTM_SCO_PKT_STATUS* get_pkt_status() {
if (msbc_info == nullptr) {
return nullptr;
}
return msbc_info->pkt_status;
}
} // namespace wbs
// TODO(b/269970706): fill `pkt_status` and allow `debug_dump`
namespace swb {
/* Second octet of H2 header is composed by 4 bits fixed 0x8 and 4 bits
* sequence number 0000, 0011, 1100, 1111. */
constexpr uint8_t btm_h2_header_frames_count[] = {0x08, 0x38, 0xc8, 0xf8};
/* Supported SCO packet sizes for LC3. The SWB-LC3 frame parsing
* code ties to limited packet size values. Specifically list them out
* to check against when setting packet size. The first entry is the default
* value as a fallback. */
constexpr size_t btm_swb_supported_pkt_size[] = {BTM_LC3_PKT_LEN, 72, 0};
/* Buffer size should be set to least common multiple of SCO packet size and
* BTM_LC3_PKT_LEN for optimizing buffer copy. */
constexpr size_t btm_swb_lc3_buffer_size[] = {BTM_LC3_PKT_LEN, 360, 0};
/* Define the structure that contains LC3 data */
struct tBTM_LC3_INFO {
size_t packet_size; /* SCO LC3 packet size supported by lower layer */
size_t buf_size; /* The size of the buffer, determined by the packet_size. */
uint8_t* lc3_decode_buf; /* Buffer to store LC3 packets to decode */
size_t decode_buf_wo; /* Write offset of the decode buffer */
size_t decode_buf_ro; /* Read offset of the decode buffer */
bool read_corrupted; /* If the current LC3 packet read is corrupted */
uint8_t* lc3_encode_buf; /* Buffer to store the encoded SCO packets */
size_t encode_buf_wo; /* Write offset of the encode buffer */
size_t encode_buf_ro; /* Read offset of the encode buffer */
int16_t decoded_pcm_buf[BTM_LC3_FS]; /* Buffer to store decoded PCM */
uint8_t num_encoded_lc3_pkts; /* Number of the encoded LC3 packets */
tBTM_SCO_PKT_STATUS* pkt_status; /* Record of LC3 packet status */
static size_t get_supported_packet_size(size_t pkt_size,
size_t* buffer_size) {
int i;
for (i = 0; btm_swb_supported_pkt_size[i] != 0 &&
btm_swb_supported_pkt_size[i] != pkt_size;
i++)
;
/* In case of unsupported value, error log and fallback to
* BTM_LC3_PKT_LEN(60). */
if (btm_swb_supported_pkt_size[i] == 0) {
LOG_WARN("Unsupported packet size %lu", (unsigned long)pkt_size);
i = 0;
}
if (buffer_size) {
*buffer_size = btm_swb_lc3_buffer_size[i];
}
return btm_swb_supported_pkt_size[i];
}
bool verify_h2_header_seq_num(const uint8_t num) {
for (int i = 0; i < 4; i++) {
if (num == btm_h2_header_frames_count[i]) {
return true;
}
}
return false;
}
public:
size_t init(size_t pkt_size) {
decode_buf_wo = 0;
decode_buf_ro = 0;
encode_buf_wo = 0;
encode_buf_ro = 0;
pkt_size = get_supported_packet_size(pkt_size, &buf_size);
if (pkt_size == packet_size) return packet_size;
packet_size = pkt_size;
if (lc3_decode_buf) osi_free(lc3_decode_buf);
lc3_decode_buf = (uint8_t*)osi_calloc(buf_size);
if (lc3_encode_buf) osi_free(lc3_encode_buf);
lc3_encode_buf = (uint8_t*)osi_calloc(buf_size);
if (pkt_status) osi_free(pkt_status);
pkt_status = (tBTM_SCO_PKT_STATUS*)osi_calloc(sizeof(*pkt_status));
pkt_status->init();
return packet_size;
}
void deinit() {
if (lc3_decode_buf) osi_free(lc3_decode_buf);
if (lc3_encode_buf) osi_free(lc3_encode_buf);
if (pkt_status) osi_free_and_reset((void**)&pkt_status);
}
size_t decodable() { return decode_buf_wo - decode_buf_ro; }
uint8_t* fill_lc3_pkt_template() {
uint8_t* wp = &lc3_encode_buf[encode_buf_wo];
if (buf_size - encode_buf_wo < BTM_LC3_PKT_LEN) {
LOG_DEBUG("Packet queue can't accommodate more packets.");
return nullptr;
}
wp[0] = BTM_LC3_H2_HEADER_0;
wp[1] = btm_h2_header_frames_count[num_encoded_lc3_pkts % 4];
encode_buf_wo += BTM_LC3_PKT_LEN;
num_encoded_lc3_pkts++;
return wp + BTM_LC3_H2_HEADER_LEN;
}
void mark_pkt_decoded() {
if (decode_buf_ro + BTM_LC3_PKT_LEN > decode_buf_wo) {
LOG_ERROR("Trying to mark read offset beyond write offset.");
return;
}
decode_buf_ro += BTM_LC3_PKT_LEN;
if (decode_buf_ro == decode_buf_wo) {
decode_buf_ro = 0;
decode_buf_wo = 0;
}
}
size_t write(const std::vector<uint8_t>& input) {
if (input.size() > buf_size - decode_buf_wo) {
return 0;
}
std::copy(input.begin(), input.end(), lc3_decode_buf + decode_buf_wo);
decode_buf_wo += input.size();
return input.size();
}
const uint8_t* find_lc3_pkt_head() {
if (read_corrupted) {
LOG_DEBUG("Skip corrupted LC3 packets");
read_corrupted = false;
return nullptr;
}
size_t rp = 0;
while (rp < BTM_LC3_PKT_LEN &&
decode_buf_wo - (decode_buf_ro + rp) >= BTM_LC3_PKT_LEN) {
if ((lc3_decode_buf[decode_buf_ro + rp] != BTM_LC3_H2_HEADER_0) ||
!verify_h2_header_seq_num(lc3_decode_buf[decode_buf_ro + rp + 1])) {
rp++;
continue;
}
if (rp != 0) {
LOG_WARN("Skipped %lu bytes of LC3 data ahead of a valid LC3 frame",
(unsigned long)rp);
decode_buf_ro += rp;
}
return &lc3_decode_buf[decode_buf_ro];
}
return nullptr;
}
size_t mark_pkt_dequeued() {
LOG_DEBUG(
"Try to mark an encoded packet dequeued: ro:%lu wo:%lu pkt_size:%lu",
(unsigned long)encode_buf_ro, (unsigned long)encode_buf_wo,
(unsigned long)packet_size);
if (encode_buf_wo - encode_buf_ro < packet_size) return 0;
encode_buf_ro += packet_size;
if (encode_buf_ro == encode_buf_wo) {
encode_buf_ro = 0;
encode_buf_wo = 0;
}
return packet_size;
}
const uint8_t* sco_pkt_read_ptr() {
if (encode_buf_wo - encode_buf_ro < packet_size) {
LOG_DEBUG("Insufficient data as a SCO packet to read.");
return nullptr;
}
return &lc3_encode_buf[encode_buf_ro];
}
};
static tBTM_LC3_INFO* lc3_info;
static int decoded_frames;
static int lost_frames;
size_t init(size_t pkt_size) {
GetInterfaceToProfiles()->lc3Codec->initialize();
decoded_frames = 0;
lost_frames = 0;
if (lc3_info) {
LOG_WARN("Re-initiating LC3 buffer that is active or not cleaned");
lc3_info->deinit();
osi_free(lc3_info);
}
lc3_info = (tBTM_LC3_INFO*)osi_calloc(sizeof(*lc3_info));
return lc3_info->init(pkt_size);
}
void cleanup() {
GetInterfaceToProfiles()->lc3Codec->cleanup();
decoded_frames = 0;
lost_frames = 0;
if (lc3_info == nullptr) return;
lc3_info->deinit();
osi_free_and_reset((void**)&lc3_info);
}
bool fill_plc_stats(int* num_decoded_frames, double* packet_loss_ratio) {
if (lc3_info == NULL || num_decoded_frames == NULL ||
packet_loss_ratio == NULL)
return false;
if (decoded_frames <= 0 || lost_frames < 0 || lost_frames > decoded_frames)
return false;
*num_decoded_frames = decoded_frames;
*packet_loss_ratio = (double)lost_frames / decoded_frames;
return true;
}
bool enqueue_packet(const std::vector<uint8_t>& data, bool corrupted) {
if (lc3_info == nullptr) {
LOG_WARN("LC3 buffer uninitialized or cleaned");
return false;
}
if (data.size() != lc3_info->packet_size) {
LOG_WARN(
"Ignoring the coming packet with size %lu that is inconsistent with "
"the HAL reported packet size %lu",
(unsigned long)data.size(), (unsigned long)lc3_info->packet_size);
return false;
}
lc3_info->read_corrupted |= corrupted;
if (lc3_info->write(data) != data.size()) {
LOG_DEBUG("Fail to write packet with size %lu to buffer",
(unsigned long)data.size());
return false;
}
return true;
}
size_t decode(const uint8_t** out_data) {
const uint8_t* frame_head = nullptr;
if (lc3_info == nullptr) {
LOG_WARN("LC3 buffer uninitialized or cleaned");
return 0;
}
if (out_data == nullptr) {
LOG_WARN("%s Invalid output pointer", __func__);
return 0;
}
if (lc3_info->decodable() < BTM_LC3_PKT_LEN) {
LOG_DEBUG("No complete LC3 packet to decode");
return 0;
}
frame_head = lc3_info->find_lc3_pkt_head();
if (frame_head == nullptr) {
LOG_DEBUG("No valid LC3 packet to decode %lu, %lu",
(unsigned long)lc3_info->decode_buf_ro,
(unsigned long)lc3_info->decode_buf_wo);
}
bool plc_conducted = !GetInterfaceToProfiles()->lc3Codec->decodePacket(
frame_head, lc3_info->decoded_pcm_buf, sizeof(lc3_info->decoded_pcm_buf));
lc3_info->pkt_status->update(plc_conducted);
++decoded_frames;
lost_frames += plc_conducted;
*out_data = (const uint8_t*)lc3_info->decoded_pcm_buf;
lc3_info->mark_pkt_decoded();
return BTM_LC3_CODE_SIZE;
}
size_t encode(int16_t* data, size_t len) {
uint8_t* pkt_body = nullptr;
if (lc3_info == nullptr) {
LOG_WARN("LC3 buffer uninitialized or cleaned");
return 0;
}
if (data == nullptr) {
LOG_WARN("Invalid data to encode");
return 0;
}
if (len < BTM_LC3_CODE_SIZE) {
LOG_DEBUG(
"PCM frames with size %lu is insufficient to be encoded into a LC3 "
"packet",
(unsigned long)len);
return 0;
}
pkt_body = lc3_info->fill_lc3_pkt_template();
if (pkt_body == nullptr) {
LOG_DEBUG("Failed to fill the template to fill the LC3 packet");
return 0;
}
return GetInterfaceToProfiles()->lc3Codec->encodePacket(data, pkt_body);
}
size_t dequeue_packet(const uint8_t** output) {
if (lc3_info == nullptr) {
LOG_WARN("LC3 buffer uninitialized or cleaned");
return 0;
}
if (output == nullptr) {
LOG_WARN("%s Invalid output pointer", __func__);
return 0;
}
*output = lc3_info->sco_pkt_read_ptr();
if (*output == nullptr) {
LOG_DEBUG("Insufficient data to dequeue.");
return 0;
}
return lc3_info->mark_pkt_dequeued();
}
tBTM_SCO_PKT_STATUS* get_pkt_status() {
if (lc3_info == nullptr) {
return nullptr;
}
return lc3_info->pkt_status;
}
} // namespace swb
} // namespace sco
} // namespace audio
} // namespace bluetooth