From d97d9fb7c9d24fab1aa1545aa15c4810595d678b Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 12 Aug 2019 21:19:14 -0700 Subject: [PATCH] add BT sink --- components/driver_bt/bt_app_sink.c | 482 ++++++++++++++++++ components/driver_bt/bt_app_sink.h | 22 + .../{bt_app_handler.c => bt_app_source.c} | 2 +- components/squeezelite/component.mk | 3 +- components/squeezelite/decode.c | 4 + components/squeezelite/decode_bt.c | 141 +++++ components/squeezelite/embedded.h | 3 +- components/squeezelite/output_embedded.c | 6 +- components/squeezelite/output_i2s.c | 3 +- components/squeezelite/squeezelite.h | 2 +- main/Kconfig.projbuild | 21 + 11 files changed, 682 insertions(+), 7 deletions(-) create mode 100644 components/driver_bt/bt_app_sink.c create mode 100644 components/driver_bt/bt_app_sink.h rename components/driver_bt/{bt_app_handler.c => bt_app_source.c} (99%) create mode 100644 components/squeezelite/decode_bt.c diff --git a/components/driver_bt/bt_app_sink.c b/components/driver_bt/bt_app_sink.c new file mode 100644 index 00000000..ef323f5a --- /dev/null +++ b/components/driver_bt/bt_app_sink.c @@ -0,0 +1,482 @@ + +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include "esp_log.h" + +#include "bt_app_core.h" +#include "bt_app_sink.h" +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "sys/lock.h" + +// AVRCP used transaction label +#define APP_RC_CT_TL_GET_CAPS (0) +#define APP_RC_CT_TL_GET_META_DATA (1) +#define APP_RC_CT_TL_RN_TRACK_CHANGE (2) +#define APP_RC_CT_TL_RN_PLAYBACK_CHANGE (3) +#define APP_RC_CT_TL_RN_PLAY_POS_CHANGE (4) + +#define BT_AV_TAG "BT_AV" +#define BT_RC_TG_TAG "RCTG" +#define BT_RC_CT_TAG "RCCT" + +#ifndef CONFIG_BT_SINK_NAME +#define CONFIG_BT_SINK_NAME "unavailable" +#endif + +/* event for handler "bt_av_hdl_stack_up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +static void (*bt_app_a2d_cmd_cb)(bt_sink_cmd_t cmd, ...); +static void (*bt_app_a2d_data_cb)(const uint8_t *data, uint32_t len); + +/* handler for bluetooth stack enabled events */ +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); +/* a2dp event handler */ +static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param); +/* avrc CT event handler */ +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param); +/* avrc TG event handler */ +static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param); + +static esp_a2d_audio_state_t s_audio_state = ESP_A2D_AUDIO_STATE_STOPPED; +static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"}; +static const char *s_a2d_audio_state_str[] = {"Suspended", "Stopped", "Started"}; +static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; +static _lock_t s_volume_lock; +static uint8_t s_volume = 0; +static bool s_volume_notify; + +/* callback for A2DP sink */ +void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: { + bt_app_work_dispatch(bt_av_hdl_a2d_evt, event, param, sizeof(esp_a2d_cb_param_t), NULL); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); + break; + } +} + +void bt_app_alloc_meta_buffer(esp_avrc_ct_cb_param_t *param) +{ + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(param); + uint8_t *attr_text = (uint8_t *) malloc (rc->meta_rsp.attr_length + 1); + memcpy(attr_text, rc->meta_rsp.attr_text, rc->meta_rsp.attr_length); + attr_text[rc->meta_rsp.attr_length] = 0; + + rc->meta_rsp.attr_text = attr_text; +} + +void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_CT_METADATA_RSP_EVT: + bt_app_alloc_meta_buffer(param); + /* fall through */ + case ESP_AVRC_CT_CONNECTION_STATE_EVT: + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL); + break; + } + default: + ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_TG_CONNECTION_STATE_EVT: + case ESP_AVRC_TG_REMOTE_FEATURES_EVT: + case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: + case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: + case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: + bt_app_work_dispatch(bt_av_hdl_avrc_tg_evt, event, param, sizeof(esp_avrc_tg_cb_param_t), NULL); + break; + default: + ESP_LOGE(BT_RC_TG_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event); + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(p_param); + uint8_t *bda = a2d->conn_stat.remote_bda; + ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]", + s_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + (*bt_app_a2d_cmd_cb)(BT_SINK_DISCONNECTED); + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){ + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + (*bt_app_a2d_cmd_cb)(BT_SINK_CONNECTED); + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(p_param); + ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]); + s_audio_state = a2d->audio_stat.state; + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { + (*bt_app_a2d_cmd_cb)(BT_SINK_PLAY); + } else if (ESP_A2D_AUDIO_STATE_STOPPED == a2d->audio_stat.state) { + (*bt_app_a2d_cmd_cb)(BT_SINK_STOP); + } + break; + } + case ESP_A2D_AUDIO_CFG_EVT: { + a2d = (esp_a2d_cb_param_t *)(p_param); + ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type %d", a2d->audio_cfg.mcc.type); + // for now only SBC stream is supported + if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) { + int sample_rate = 16000; + char oct0 = a2d->audio_cfg.mcc.cie.sbc[0]; + if (oct0 & (0x01 << 6)) { + sample_rate = 32000; + } else if (oct0 & (0x01 << 5)) { + sample_rate = 44100; + } else if (oct0 & (0x01 << 4)) { + sample_rate = 48000; + } + (*bt_app_a2d_cmd_cb)(BT_SINK_RATE, sample_rate); + + ESP_LOGI(BT_AV_TAG, "Configure audio player %x-%x-%x-%x", + a2d->audio_cfg.mcc.cie.sbc[0], + a2d->audio_cfg.mcc.cie.sbc[1], + a2d->audio_cfg.mcc.cie.sbc[2], + a2d->audio_cfg.mcc.cie.sbc[3]); + ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate=%d", sample_rate); + } + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_av_new_track(void) +{ + // request metadata + uint8_t attr_mask = ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST | ESP_AVRC_MD_ATTR_ALBUM | ESP_AVRC_MD_ATTR_GENRE; + esp_avrc_ct_send_metadata_cmd(APP_RC_CT_TL_GET_META_DATA, attr_mask); + + // register notification if peer support the event_id + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_TRACK_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_TRACK_CHANGE, ESP_AVRC_RN_TRACK_CHANGE, 0); + } +} + +static void bt_av_playback_changed(void) +{ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_PLAY_STATUS_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_PLAYBACK_CHANGE, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0); + } +} + +static void bt_av_play_pos_changed(void) +{ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_PLAY_POS_CHANGED)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_PLAY_POS_CHANGE, ESP_AVRC_RN_PLAY_POS_CHANGED, 10); + } +} + +void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter) +{ + switch (event_id) { + case ESP_AVRC_RN_TRACK_CHANGE: + bt_av_new_track(); + break; + case ESP_AVRC_RN_PLAY_STATUS_CHANGE: + ESP_LOGI(BT_AV_TAG, "Playback status changed: 0x%x", event_parameter->playback); + bt_av_playback_changed(); + break; + case ESP_AVRC_RN_PLAY_POS_CHANGED: + ESP_LOGI(BT_AV_TAG, "Play position changed: %d-ms", event_parameter->play_pos); + bt_av_play_pos_changed(); + break; + } +} + +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event); + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param); + switch (event) { + case ESP_AVRC_CT_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + + if (rc->conn_stat.connected) { + // get remote supported event_ids of peer AVRCP Target + esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS); + } else { + // clear peer notification capability record + s_avrc_peer_rn_cap.bits = 0; + } + break; + } + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state); + break; + } + case ESP_AVRC_CT_METADATA_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); + free(rc->meta_rsp.attr_text); + break; + } + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id); + bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter); + break; + } + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag); + break; + } + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count, + rc->get_rn_caps_rsp.evt_set.bits); + s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits; + bt_av_new_track(); + bt_av_playback_changed(); + bt_av_play_pos_changed(); + break; + } + default: + ESP_LOGE(BT_RC_CT_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void volume_set_by_controller(uint8_t volume) +{ + ESP_LOGI(BT_RC_TG_TAG, "Volume is set by remote controller %d%%\n", (uint32_t)volume * 100 / 0x7f); + _lock_acquire(&s_volume_lock); + s_volume = volume; + _lock_release(&s_volume_lock); + (*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, volume); +} + +static void volume_set_by_local_host(uint8_t volume) +{ + ESP_LOGI(BT_RC_TG_TAG, "Volume is set locally to: %d%%", (uint32_t)volume * 100 / 0x7f); + _lock_acquire(&s_volume_lock); + s_volume = volume; + _lock_release(&s_volume_lock); + + if (s_volume_notify) { + esp_avrc_rn_param_t rn_param; + rn_param.volume = s_volume; + esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_CHANGED, &rn_param); + s_volume_notify = false; + } +} + +static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_RC_TG_TAG, "%s evt %d", __func__, event); + esp_avrc_tg_cb_param_t *rc = (esp_avrc_tg_cb_param_t *)(p_param); + switch (event) { + case ESP_AVRC_TG_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_TG_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + break; + } + case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC passthrough cmd: key_code 0x%x, key_state %d", rc->psth_cmd.key_code, rc->psth_cmd.key_state); + break; + } + case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC set absolute volume: %d%%", (int)rc->set_abs_vol.volume * 100/ 0x7f); + volume_set_by_controller(rc->set_abs_vol.volume); + break; + } + case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC register event notification: %d, param: 0x%x", rc->reg_ntf.event_id, rc->reg_ntf.event_parameter); + if (rc->reg_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) { + s_volume_notify = true; + esp_avrc_rn_param_t rn_param; + rn_param.volume = s_volume; + esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_INTERIM, &rn_param); + } + break; + } + case ESP_AVRC_TG_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC remote features %x, CT features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag); + break; + } + default: + ESP_LOGE(BT_RC_TG_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +void bt_sink_init(void (*cmd_cb)(bt_sink_cmd_t cmd, ...), void (*data_cb)(const uint8_t *data, uint32_t len)) +{ + esp_err_t err; + + bt_app_a2d_cmd_cb = cmd_cb; + bt_app_a2d_data_cb = data_cb; + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + if ((err = esp_bluedroid_init()) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + if ((err = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + /* create application task */ + bt_app_task_start_up(); + + /* Bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); + +#if (CONFIG_BT_SSP_ENABLED == true) + /* Set default parameters for Secure Simple Pairing */ + esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE; + esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO; + esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t)); +#endif + + /* + * Set default parameters for Legacy Pairing + * Use fixed pin code + */ + esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED; + esp_bt_pin_code_t pin_code; + pin_code[0] = '1'; + pin_code[1] = '2'; + pin_code[2] = '3'; + pin_code[3] = '4'; + esp_bt_gap_set_pin(pin_type, 4, pin_code); + +} + +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch (event) { + case ESP_BT_GAP_AUTH_CMPL_EVT: { + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "authentication success: %s", param->auth_cmpl.device_name); + esp_log_buffer_hex(BT_AV_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(BT_AV_TAG, "authentication failed, status:%d", param->auth_cmpl.stat); + } + break; + } + +#if (CONFIG_BT_SSP_ENABLED == true) + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val); + esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true); + break; + case ESP_BT_GAP_KEY_NOTIF_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + default: { + ESP_LOGI(BT_AV_TAG, "event: %d", event); + break; + } + } + return; +} + +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event); + switch (event) { + case BT_APP_EVT_STACK_UP: { + /* set up device name */ + char *dev_name = CONFIG_BT_SINK_NAME; + esp_bt_dev_set_device_name(dev_name); + + esp_bt_gap_register_callback(bt_app_gap_cb); + + /* initialize AVRCP controller */ + esp_avrc_ct_init(); + esp_avrc_ct_register_callback(bt_app_rc_ct_cb); + /* initialize AVRCP target */ + assert (esp_avrc_tg_init() == ESP_OK); + esp_avrc_tg_register_callback(bt_app_rc_tg_cb); + + esp_avrc_rn_evt_cap_mask_t evt_set = {0}; + esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE); + assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK); + + /* initialize A2DP sink */ + esp_a2d_register_callback(&bt_app_a2d_cb); + esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb); + esp_a2d_sink_init(); + + /* set discoverable and connectable mode, wait to be connected */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + diff --git a/components/driver_bt/bt_app_sink.h b/components/driver_bt/bt_app_sink.h new file mode 100644 index 00000000..9c84446e --- /dev/null +++ b/components/driver_bt/bt_app_sink.h @@ -0,0 +1,22 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#ifndef __BT_APP_SINK_H__ +#define __BT_APP_SINK_H__ + +#include + +typedef enum { BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE, + BT_SINK_RATE, BT_SINK_VOLUME, } bt_sink_cmd_t; + +/** + * @brief init sink mode (need to be provided) + */ +void bt_sink_init(void (*cmd_cb)(bt_sink_cmd_t cmd, ...), void (*data_cb)(const uint8_t *data, uint32_t len)); + +#endif /* __BT_APP_SINK_H__*/ diff --git a/components/driver_bt/bt_app_handler.c b/components/driver_bt/bt_app_source.c similarity index 99% rename from components/driver_bt/bt_app_handler.c rename to components/driver_bt/bt_app_source.c index cf24d351..f5a0f955 100644 --- a/components/driver_bt/bt_app_handler.c +++ b/components/driver_bt/bt_app_source.c @@ -259,7 +259,7 @@ static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL); } -void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) { switch (event) { diff --git a/components/squeezelite/component.mk b/components/squeezelite/component.mk index 7bc7786f..1d9abe13 100644 --- a/components/squeezelite/component.mk +++ b/components/squeezelite/component.mk @@ -12,7 +12,8 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON -I$(COMPONENT_PATH)/../codecs/inc/resample16 \ -I$(COMPONENT_PATH)/../tools \ -I$(COMPONENT_PATH)/../codecs/inc/opus \ - -I$(COMPONENT_PATH)/../codecs/inc/opusfile + -I$(COMPONENT_PATH)/../codecs/inc/opusfile \ + -I$(COMPONENT_PATH)/../driver_bt # -I$(COMPONENT_PATH)/../codecs/inc/faad2 diff --git a/components/squeezelite/decode.c b/components/squeezelite/decode.c index 83e16ed9..333af5df 100644 --- a/components/squeezelite/decode.c +++ b/components/squeezelite/decode.c @@ -198,6 +198,10 @@ void decode_init(log_level level, const char *include_codecs, const char *exclud else if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mpg")) && (!include_codecs || (order_codecs = strstr(include_codecs, "mp3")) || (order_codecs = strstr(include_codecs, "mpg")))) sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mpg()); + +#if EMBEDDED + register_other(); +#endif LOG_DEBUG("include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs); diff --git a/components/squeezelite/decode_bt.c b/components/squeezelite/decode_bt.c new file mode 100644 index 00000000..572b8860 --- /dev/null +++ b/components/squeezelite/decode_bt.c @@ -0,0 +1,141 @@ +/* + * Squeezelite for esp32 + * + * (c) Sebastien 2019 + * Philippe G. 2019, philippe_44@outlook.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "squeezelite.h" +#include "bt_app_sink.h" + +#define LOCK_O mutex_lock(outputbuf->mutex) +#define UNLOCK_O mutex_unlock(outputbuf->mutex) +#define LOCK_D mutex_lock(decode.mutex); +#define UNLOCK_D mutex_unlock(decode.mutex); + +extern struct outputstate output; +extern struct decodestate decode; +extern struct buffer *outputbuf; +// this is the only system-wide loglevel variable +extern log_level loglevel; + +/**************************************************************************************** + * BT sink data handler + */ +static void bt_sink_data_handler(const uint8_t *data, uint32_t len) +{ + size_t bytes; + + // would be better to lock decoder, but really, it does not matter + if (decode.state != DECODE_STOPPED) { + LOG_WARN("Cannot use BT sink while LMS is controlling player"); + return; + } + + // there will always be room at some point + while (len) { + LOCK_O; + + bytes = min(len, _buf_cont_write(outputbuf)); +#if BYTES_PER_FRAME == 4 + memcpy(outputbuf->writep, data, bytes); +#else + { + s16_t *iptr = (s16_t*) data; + ISAMPLE_T *optr = (ISAMPLE_T*) outputbuf->writep; + size_t n = bytes / BYTES_PER_FRAME * 2; + while (n--) *optr++ = *iptr++ << 16; + } +#endif + _buf_inc_writep(outputbuf, bytes); + len -= bytes; + data += bytes; + + UNLOCK_O; + + // allow i2s to empty the buffer if needed + if (len) usleep(50000); + } +} + +/**************************************************************************************** + * BT sink command handler + */ +static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...) +{ + va_list args; + + LOCK_D; + if (decode.state != DECODE_STOPPED) { + LOG_WARN("Cannot use BT sink while LMS is controlling player"); + UNLOCK_D; + return; + } + + va_start(args, cmd); + + if (cmd != BT_SINK_VOLUME) LOCK_O; + + switch(cmd) { + case BT_SINK_CONNECTED: + output.state = OUTPUT_STOPPED; + LOG_INFO("BT sink started"); + break; + case BT_SINK_DISCONNECTED: + output.state = OUTPUT_OFF; + LOG_INFO("BT sink stopped"); + break; + case BT_SINK_PLAY: + output.state = OUTPUT_EXTERNAL; + LOG_INFO("BT sink playing"); + break; + case BT_SINK_PAUSE: + case BT_SINK_STOP: + output.state = OUTPUT_STOPPED; + LOG_INFO("BT sink stopped"); + break; + case BT_SINK_RATE: + output.current_sample_rate = va_arg(args, u32_t); + LOG_INFO("Setting BT sample rate %u", output.current_sample_rate); + break; + case BT_SINK_VOLUME: { + u16_t volume = (u16_t) va_arg(args, u32_t); + volume *= 65536 / 128; + set_volume(volume, volume); + break; + } + } + + if (cmd != BT_SINK_VOLUME) UNLOCK_O; + UNLOCK_D; + + va_end(args); +} + +/**************************************************************************************** + * We provide the generic codec register option + */ +void register_other(void) { +#ifdef CONFIG_BT_SINK + if (!strcasestr(output.device, "BT ")) { + bt_sink_init(bt_sink_cmd_handler, bt_sink_data_handler); + LOG_INFO("Initializing BT sink"); + } else { + LOG_WARN("Cannot be a BT sink and source"); + } +#endif +} diff --git a/components/squeezelite/embedded.h b/components/squeezelite/embedded.h index 8bbd6b0c..f96c6c24 100644 --- a/components/squeezelite/embedded.h +++ b/components/squeezelite/embedded.h @@ -39,5 +39,6 @@ typedef unsigned long long u64_t; uint32_t _gettime_ms_(void); int pthread_create_name(pthread_t *thread, _CONST pthread_attr_t *attr, void *(*start_routine)( void * ), void *arg, char *name); - +void register_other(void); + #endif // EMBEDDED_H diff --git a/components/squeezelite/output_embedded.c b/components/squeezelite/output_embedded.c index 32af3f42..ab758587 100644 --- a/components/squeezelite/output_embedded.c +++ b/components/squeezelite/output_embedded.c @@ -37,6 +37,10 @@ extern void output_init_i2s(log_level level, char *device, unsigned output_buf_s extern bool output_volume_i2s(unsigned left, unsigned right); extern void output_close_i2s(void); +#ifdef CONFIG_BT_SINK +extern void decode_bt_init(void); +#endif + static log_level loglevel; static bool (*volume_cb)(unsigned left, unsigned right); @@ -52,7 +56,7 @@ void output_init_embedded(log_level level, char *device, unsigned output_buf_siz output.start_frames = FRAME_BLOCK; output.rate_delay = rate_delay; - if (strstr(device, "BT ")) { + if (strcasestr(device, "BT ")) { LOG_INFO("init Bluetooth"); close_cb = &output_close_bt; output_init_bt(level, device, output_buf_size, params, rates, rate_delay, idle); diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index 649e5ef4..f8f2132b 100644 --- a/components/squeezelite/output_i2s.c +++ b/components/squeezelite/output_i2s.c @@ -414,7 +414,7 @@ static void *output_thread_i2s() { // manage led display if (state != output.state) { - LOG_INFO("Output state is %u", output.state); + LOG_INFO("Output state is %d", output.state); if (output.state == OUTPUT_OFF) led_blink(LED_GREEN, 100, 2500); else if (output.state == OUTPUT_STOPPED) led_blink(LED_GREEN, 200, 1000); else if (output.state == OUTPUT_RUNNING) led_on(LED_GREEN); @@ -604,7 +604,6 @@ extern const u16_t spdif_bmclookup[256]; */ void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count) { u16_t hi, lo, aux; - size_t pos; // frames are 2 channels of 16 bits frames *= 2; diff --git a/components/squeezelite/squeezelite.h b/components/squeezelite/squeezelite.h index 9673e10e..b51247a3 100644 --- a/components/squeezelite/squeezelite.h +++ b/components/squeezelite/squeezelite.h @@ -634,7 +634,7 @@ bool resample_init(char *opt); // output.c output_alsa.c output_pa.c output_pack.c typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING, - OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT } output_state; + OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT, OUTPUT_EXTERNAL } output_state; #if DSD typedef enum { PCM, DOP, DSD_U8, DSD_U16_LE, DSD_U32_LE, DSD_U16_BE, DSD_U32_BE, DOP_S24_LE, DOP_S24_3LE } dsd_format; diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 9d50fb0c..b009ab04 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -219,5 +219,26 @@ menu "Squeezelite-ESP32" Increasing this value will give more chance for less stable connections to be established. endmenu endmenu + + menu "Audio Input" + config BT_SINK + bool "Bluetooth receiver" + default y + help + Enable bluetooth sink (Note that you obviously can't at the same time be a Bluetooth receiver and transmitter) + config BT_SINK_NAME + depends on BT_SINK + string "Name of Bluetooth A2DP device" + default "ESP32" + help + This is the name of the bluetooth speaker that will be broadcasted + config BT_SINK_PIN + depends on BT_SINK + int "Bluetooth PIN code" + default 1234 + config AIRPLAY_SINK + bool "AirPlay receiver (not availabe now)" + default n + endmenu endmenu \ No newline at end of file