/******************************************************************************
*
* Copyright 2018 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
#include
#include
#include
#include
#include "audio_hal_interface/hearing_aid_software_encoding.h"
#include "audio_hearing_aid_hw/include/audio_hearing_aid_hw.h"
#include "bta/include/bta_hearing_aid_api.h"
#include "common/repeating_timer.h"
#include "common/time_util.h"
#include "osi/include/wakelock.h"
#include "stack/include/btu.h" // get_main_thread
#include "udrv/include/uipc.h"
using base::FilePath;
namespace {
#define CASE_RETURN_STR(const) \
case const: \
return #const;
const char* audio_ha_hw_dump_ctrl_event(tHEARING_AID_CTRL_CMD event) {
switch (event) {
CASE_RETURN_STR(HEARING_AID_CTRL_CMD_NONE)
CASE_RETURN_STR(HEARING_AID_CTRL_CMD_CHECK_READY)
CASE_RETURN_STR(HEARING_AID_CTRL_CMD_START)
CASE_RETURN_STR(HEARING_AID_CTRL_CMD_STOP)
CASE_RETURN_STR(HEARING_AID_CTRL_CMD_SUSPEND)
CASE_RETURN_STR(HEARING_AID_CTRL_GET_INPUT_AUDIO_CONFIG)
CASE_RETURN_STR(HEARING_AID_CTRL_GET_OUTPUT_AUDIO_CONFIG)
CASE_RETURN_STR(HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG)
CASE_RETURN_STR(HEARING_AID_CTRL_CMD_OFFLOAD_START)
default:
break;
}
return "UNKNOWN HEARING_AID_CTRL_CMD";
}
int bit_rate = -1;
int sample_rate = -1;
int data_interval_ms = -1;
int num_channels = 2;
bluetooth::common::RepeatingTimer audio_timer;
HearingAidAudioReceiver* localAudioReceiver = nullptr;
std::unique_ptr uipc_hearing_aid = nullptr;
struct AudioHalStats {
size_t media_read_total_underflow_bytes;
size_t media_read_total_underflow_count;
uint64_t media_read_last_underflow_us;
AudioHalStats() { Reset(); }
void Reset() {
media_read_total_underflow_bytes = 0;
media_read_total_underflow_count = 0;
media_read_last_underflow_us = 0;
}
};
AudioHalStats stats;
bool hearing_aid_on_resume_req(bool start_media_task);
bool hearing_aid_on_suspend_req();
void send_audio_data() {
uint32_t bytes_per_tick =
(num_channels * sample_rate * data_interval_ms * (bit_rate / 8)) / 1000;
uint16_t event;
uint8_t p_buf[bytes_per_tick];
uint32_t bytes_read;
if (bluetooth::audio::hearing_aid::is_hal_2_0_enabled()) {
bytes_read = bluetooth::audio::hearing_aid::read(p_buf, bytes_per_tick);
} else {
bytes_read = UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO, &event,
p_buf, bytes_per_tick);
}
VLOG(2) << "bytes_read: " << bytes_read;
if (bytes_read < bytes_per_tick) {
stats.media_read_total_underflow_bytes += bytes_per_tick - bytes_read;
stats.media_read_total_underflow_count++;
stats.media_read_last_underflow_us =
bluetooth::common::time_get_os_boottime_us();
}
std::vector data(p_buf, p_buf + bytes_read);
if (localAudioReceiver != nullptr) {
localAudioReceiver->OnAudioDataReady(data);
}
}
void hearing_aid_send_ack(tHEARING_AID_CTRL_ACK status) {
uint8_t ack = status;
DVLOG(2) << "Hearing Aid audio ctrl ack: " << status;
UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0, &ack, sizeof(ack));
}
void start_audio_ticks() {
if (data_interval_ms != HA_INTERVAL_10_MS &&
data_interval_ms != HA_INTERVAL_20_MS) {
LOG(FATAL) << " Unsupported data interval: " << data_interval_ms;
}
wakelock_acquire();
audio_timer.SchedulePeriodic(
get_main_thread()->GetWeakPtr(), FROM_HERE, base::Bind(&send_audio_data),
base::TimeDelta::FromMilliseconds(data_interval_ms));
LOG(INFO) << __func__ << ": running with data interval: " << data_interval_ms;
}
void stop_audio_ticks() {
LOG(INFO) << __func__ << ": stopped";
audio_timer.CancelAndWait();
wakelock_release();
}
void hearing_aid_data_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
DVLOG(2) << "Hearing Aid audio data event: " << event;
switch (event) {
case UIPC_OPEN_EVT:
LOG(INFO) << __func__ << ": UIPC_OPEN_EVT";
/*
* Read directly from media task from here on (keep callback for
* connection events.
*/
UIPC_Ioctl(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO,
UIPC_REG_REMOVE_ACTIVE_READSET, NULL);
UIPC_Ioctl(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO, UIPC_SET_READ_POLL_TMO,
reinterpret_cast(0));
do_in_main_thread(FROM_HERE, base::BindOnce(start_audio_ticks));
break;
case UIPC_CLOSE_EVT:
LOG(INFO) << __func__ << ": UIPC_CLOSE_EVT";
hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
do_in_main_thread(FROM_HERE, base::BindOnce(stop_audio_ticks));
break;
default:
LOG(ERROR) << "Hearing Aid audio data event not recognized:" << event;
}
}
void hearing_aid_recv_ctrl_data() {
tHEARING_AID_CTRL_CMD cmd = HEARING_AID_CTRL_CMD_NONE;
int n;
uint8_t read_cmd = 0; /* The read command size is one octet */
n = UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, NULL, &read_cmd, 1);
cmd = static_cast(read_cmd);
/* detach on ctrl channel means audioflinger process was terminated */
if (n == 0) {
LOG(WARNING) << __func__ << "CTRL CH DETACHED";
UIPC_Close(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL);
return;
}
LOG(INFO) << __func__ << " " << audio_ha_hw_dump_ctrl_event(cmd);
// a2dp_cmd_pending = cmd;
tHEARING_AID_CTRL_ACK ctrl_ack_status;
switch (cmd) {
case HEARING_AID_CTRL_CMD_CHECK_READY:
hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
break;
case HEARING_AID_CTRL_CMD_START:
ctrl_ack_status = HEARING_AID_CTRL_ACK_SUCCESS;
// timer is restarted in UIPC_Open
if (!hearing_aid_on_resume_req(false)) {
ctrl_ack_status = HEARING_AID_CTRL_ACK_FAILURE;
} else {
UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO, hearing_aid_data_cb,
HEARING_AID_DATA_PATH);
}
hearing_aid_send_ack(ctrl_ack_status);
break;
case HEARING_AID_CTRL_CMD_STOP:
if (!hearing_aid_on_suspend_req()) {
LOG(INFO) << __func__ << ":HEARING_AID_CTRL_CMD_STOP: hearing_aid_on_suspend_req() errs, but ignored.";
}
hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
break;
case HEARING_AID_CTRL_CMD_SUSPEND:
ctrl_ack_status = HEARING_AID_CTRL_ACK_SUCCESS;
if (!hearing_aid_on_suspend_req()) {
ctrl_ack_status = HEARING_AID_CTRL_ACK_FAILURE;
}
hearing_aid_send_ack(ctrl_ack_status);
break;
case HEARING_AID_CTRL_GET_OUTPUT_AUDIO_CONFIG: {
btav_a2dp_codec_config_t codec_config;
btav_a2dp_codec_config_t codec_capability;
if (sample_rate == 16000) {
codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_16000;
codec_capability.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_16000;
} else if (sample_rate == 24000) {
codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_24000;
codec_capability.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_24000;
} else {
LOG(FATAL) << "unsupported sample rate: " << sample_rate;
}
codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
codec_capability.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
codec_capability.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
// Send the current codec config
UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast(&codec_config.sample_rate),
sizeof(btav_a2dp_codec_sample_rate_t));
UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast(&codec_config.bits_per_sample),
sizeof(btav_a2dp_codec_bits_per_sample_t));
UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast(&codec_config.channel_mode),
sizeof(btav_a2dp_codec_channel_mode_t));
// Send the current codec capability
UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast(&codec_capability.sample_rate),
sizeof(btav_a2dp_codec_sample_rate_t));
UIPC_Send(
*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast(&codec_capability.bits_per_sample),
sizeof(btav_a2dp_codec_bits_per_sample_t));
UIPC_Send(
*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast(&codec_capability.channel_mode),
sizeof(btav_a2dp_codec_channel_mode_t));
break;
}
case HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG: {
// TODO: we only support one config for now!
btav_a2dp_codec_config_t codec_config;
codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE;
codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE;
hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
// Send the current codec config
if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast(&codec_config.sample_rate),
sizeof(btav_a2dp_codec_sample_rate_t)) !=
sizeof(btav_a2dp_codec_sample_rate_t)) {
LOG(ERROR) << __func__ << "Error reading sample rate from audio HAL";
break;
}
if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast(&codec_config.bits_per_sample),
sizeof(btav_a2dp_codec_bits_per_sample_t)) !=
sizeof(btav_a2dp_codec_bits_per_sample_t)) {
LOG(ERROR) << __func__
<< "Error reading bits per sample from audio HAL";
break;
}
if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast(&codec_config.channel_mode),
sizeof(btav_a2dp_codec_channel_mode_t)) !=
sizeof(btav_a2dp_codec_channel_mode_t)) {
LOG(ERROR) << __func__ << "Error reading channel mode from audio HAL";
break;
}
LOG(INFO) << __func__ << " HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG: "
<< "sample_rate=" << codec_config.sample_rate
<< "bits_per_sample=" << codec_config.bits_per_sample
<< "channel_mode=" << codec_config.channel_mode;
break;
}
default:
LOG(ERROR) << __func__ << "UNSUPPORTED CMD: " << cmd;
hearing_aid_send_ack(HEARING_AID_CTRL_ACK_FAILURE);
break;
}
LOG(INFO) << __func__
<< " a2dp-ctrl-cmd : " << audio_ha_hw_dump_ctrl_event(cmd)
<< " DONE";
}
void hearing_aid_ctrl_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
VLOG(2) << "Hearing Aid audio ctrl event: " << event;
switch (event) {
case UIPC_OPEN_EVT:
break;
case UIPC_CLOSE_EVT:
/* restart ctrl server unless we are shutting down */
if (HearingAid::IsHearingAidRunning()) {
UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, hearing_aid_ctrl_cb,
HEARING_AID_CTRL_PATH);
}
break;
case UIPC_RX_DATA_READY_EVT:
hearing_aid_recv_ctrl_data();
break;
default:
LOG(ERROR) << "Hearing Aid audio ctrl unrecognized event: " << event;
}
}
bool hearing_aid_on_resume_req(bool start_media_task) {
if (localAudioReceiver == nullptr) {
LOG(ERROR) << __func__
<< ": HEARING_AID_CTRL_CMD_START: audio receiver not started";
return false;
}
bt_status_t status;
if (start_media_task) {
status = do_in_main_thread(
FROM_HERE, base::BindOnce(&HearingAidAudioReceiver::OnAudioResume,
base::Unretained(localAudioReceiver),
start_audio_ticks));
} else {
auto start_dummy_ticks = []() {
LOG(INFO) << "start_audio_ticks: waiting for data path opened";
};
status = do_in_main_thread(
FROM_HERE, base::BindOnce(&HearingAidAudioReceiver::OnAudioResume,
base::Unretained(localAudioReceiver),
start_dummy_ticks));
}
if (status != BT_STATUS_SUCCESS) {
LOG(ERROR) << __func__
<< ": HEARING_AID_CTRL_CMD_START: do_in_main_thread err="
<< status;
return false;
}
return true;
}
bool hearing_aid_on_suspend_req() {
if (localAudioReceiver == nullptr) {
LOG(ERROR) << __func__
<< ": HEARING_AID_CTRL_CMD_SUSPEND: audio receiver not started";
return false;
}
bt_status_t status = do_in_main_thread(
FROM_HERE,
base::BindOnce(&HearingAidAudioReceiver::OnAudioSuspend,
base::Unretained(localAudioReceiver), stop_audio_ticks));
if (status != BT_STATUS_SUCCESS) {
LOG(ERROR) << __func__
<< ": HEARING_AID_CTRL_CMD_SUSPEND: do_in_main_thread err="
<< status;
return false;
}
return true;
}
} // namespace
void HearingAidAudioSource::Start(const CodecConfiguration& codecConfiguration,
HearingAidAudioReceiver* audioReceiver,
uint16_t remote_delay_ms) {
LOG(INFO) << __func__ << ": Hearing Aid Source Open";
bit_rate = codecConfiguration.bit_rate;
sample_rate = codecConfiguration.sample_rate;
data_interval_ms = codecConfiguration.data_interval_ms;
stats.Reset();
if (bluetooth::audio::hearing_aid::is_hal_2_0_enabled()) {
bluetooth::audio::hearing_aid::start_session();
bluetooth::audio::hearing_aid::set_remote_delay(remote_delay_ms);
}
localAudioReceiver = audioReceiver;
}
void HearingAidAudioSource::Stop() {
LOG(INFO) << __func__ << ": Hearing Aid Source Close";
localAudioReceiver = nullptr;
if (bluetooth::audio::hearing_aid::is_hal_2_0_enabled()) {
bluetooth::audio::hearing_aid::end_session();
}
stop_audio_ticks();
}
void HearingAidAudioSource::Initialize() {
auto stream_cb = bluetooth::audio::hearing_aid::StreamCallbacks{
.on_resume_ = hearing_aid_on_resume_req,
.on_suspend_ = hearing_aid_on_suspend_req,
};
if (!bluetooth::audio::hearing_aid::init(stream_cb, get_main_thread())) {
LOG(WARNING) << __func__ << ": Using legacy HAL";
uipc_hearing_aid = UIPC_Init();
UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, hearing_aid_ctrl_cb, HEARING_AID_CTRL_PATH);
}
}
void HearingAidAudioSource::CleanUp() {
if (bluetooth::audio::hearing_aid::is_hal_2_0_enabled()) {
bluetooth::audio::hearing_aid::cleanup();
} else {
UIPC_Close(*uipc_hearing_aid, UIPC_CH_ID_ALL);
uipc_hearing_aid = nullptr;
}
}
void HearingAidAudioSource::DebugDump(int fd) {
uint64_t now_us = bluetooth::common::time_get_os_boottime_us();
std::stringstream stream;
stream << " Hearing Aid Audio HAL:"
<< "\n Counts (underflow) : "
<< stats.media_read_total_underflow_count
<< "\n Bytes (underflow) : "
<< stats.media_read_total_underflow_bytes
<< "\n Last update time ago in ms (underflow) : "
<< (stats.media_read_last_underflow_us > 0
? (unsigned long long)(now_us -
stats.media_read_last_underflow_us) /
1000
: 0)
<< std::endl;
dprintf(fd, "%s", stream.str().c_str());
}