philippe44 5 år sedan
förälder
incheckning
d97d9fb7c9

+ 482 - 0
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 <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#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;
+    }
+}
+

+ 22 - 0
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 <stdint.h>
+
+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__*/

+ 1 - 1
components/driver_bt/bt_app_handler.c → 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) {

+ 2 - 1
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
 

+ 4 - 0
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);
 

+ 141 - 0
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 <http://www.gnu.org/licenses/>.
+ *
+ */
+ 
+#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	
+}

+ 2 - 1
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

+ 5 - 1
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);

+ 1 - 2
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;

+ 1 - 1
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;

+ 21 - 0
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