diff --git a/main/esp32.c b/main/esp32.c index b758d04c..a40324fe 100644 --- a/main/esp32.c +++ b/main/esp32.c @@ -3,8 +3,65 @@ #include "sdkconfig.h" #include "esp_system.h" #include "squeezelite.h" +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/timers.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "esp_system.h" +#include "esp_log.h" + + +#include "esp_bt.h" +#include "bt_app_core.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 "esp_pthread.h" +#include "pthread.h" +#define BT_AV_TAG "BT_AV" +u8_t *bt_optr; extern log_level loglevel; +extern struct outputstate output; +extern struct buffer *outputbuf; +extern struct buffer *streambuf; +#define LOCK mutex_lock(outputbuf->mutex) +#define UNLOCK mutex_unlock(outputbuf->mutex) +int64_t connecting_timeout = 0; +#ifndef CONFIG_A2DP_SINK_NAME +#define CONFIG_A2DP_SINK_NAME "btspeaker" // fix some compile errors when BT is not chosen +#endif +#ifndef CONFIG_A2DP_CONNECT_TIMEOUT_MS +#define CONFIG_A2DP_CONNECT_TIMEOUT_MS 2000 +#endif +#ifndef CONFIG_A2DP_DEV_NAME +#define CONFIG_A2DP_DEV_NAME "espsqueezelite" +#endif +#ifndef CONFIG_A2DP_CONTROL_DELAY_MS +#define CONFIG_A2DP_CONTROL_DELAY_MS 1000 +#endif +#define A2DP_TIMER_INIT connecting_timeout = esp_timer_get_time() +(CONFIG_A2DP_CONNECT_TIMEOUT_MS * 1000) +#define IS_A2DP_TIMER_OVER esp_timer_get_time() >= connecting_timeout + +#define FRAME_TO_BYTES(f) f*BYTES_PER_FRAME +#define BYTES_TO_FRAME(b) b/BYTES_PER_FRAME +#define FRAMES_TO_MS(f) 1000*f/output.current_sample_rate +#define BYTES_TO_MS(b) FRAMES_TO_MS(BYTES_TO_FRAME(b)) + +#define SET_MIN_MAX(val,var) var=val; if(varmax_##var) max_##var=var +#define RESET_MIN_MAX(var,mv) min_##var=mv##_MAX; max_##var=mv##_MIN +#define DECLARE_MIN_MAX(var,t,mv) static t min_##var = mv##_MAX, max_##var = mv##_MIN; t var=0 +#define DECLARE_ALL_MIN_MAX DECLARE_MIN_MAX(req, long,LONG); DECLARE_MIN_MAX(o, long,LONG); DECLARE_MIN_MAX(s, long,LONG); DECLARE_MIN_MAX(d, long,LONG); +#define RESET_ALL_MIN_MAX RESET_MIN_MAX(d,LONG); RESET_MIN_MAX(o,LONG); RESET_MIN_MAX(s,LONG); RESET_MIN_MAX(req,LONG); + void get_mac(u8_t mac[]) { esp_read_mac(mac, ESP_MAC_WIFI_STA); @@ -58,3 +115,775 @@ struct codec *register_alac(void) { } #endif +#define LOG_DEBUG_EVENT(e) LOG_DEBUG("evt: " STR(e)) +#define LOG_SDEBUG_EVENT(e) LOG_SDEBUG("evt: " STR(e)) + +/* event for handler "bt_av_hdl_stack_up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/* A2DP global state */ +enum { + APP_AV_STATE_IDLE, + APP_AV_STATE_DISCOVERING, + APP_AV_STATE_DISCOVERED, + APP_AV_STATE_UNCONNECTED, + APP_AV_STATE_CONNECTING, + APP_AV_STATE_CONNECTED, + APP_AV_STATE_DISCONNECTING, +}; + +char * APP_AV_STATE_DESC[] = { + "APP_AV_STATE_IDLE", + "APP_AV_STATE_DISCOVERING", + "APP_AV_STATE_DISCOVERED", + "APP_AV_STATE_UNCONNECTED", + "APP_AV_STATE_CONNECTING", + "APP_AV_STATE_CONNECTED", + "APP_AV_STATE_DISCONNECTING" +}; + + + +/* sub states of APP_AV_STATE_CONNECTED */ + +enum { + APP_AV_MEDIA_STATE_IDLE, + APP_AV_MEDIA_STATE_STARTING, + APP_AV_MEDIA_STATE_STARTED, + APP_AV_MEDIA_STATE_STOPPING, + APP_AV_MEDIA_STATE_WAIT_DISCONNECT +}; + +#define BT_APP_HEART_BEAT_EVT (0xff00) + +/// handler for bluetooth stack enabled events +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + +/// callback function for A2DP source +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +/// callback function for A2DP source audio data stream +static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len); + +static void a2d_app_heart_beat(void *arg); + +/// A2DP application state machine +static void bt_app_av_sm_hdlr(uint16_t event, void *param); + +/* A2DP application state machine handler for each state */ +static void bt_app_av_state_unconnected(uint16_t event, void *param); +static void bt_app_av_state_connecting(uint16_t event, void *param); +static void bt_app_av_state_connected(uint16_t event, void *param); +static void bt_app_av_state_disconnecting(uint16_t event, void *param); + +static esp_bd_addr_t s_peer_bda = {0}; +static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; +static int s_a2d_state = APP_AV_STATE_IDLE; +static int s_media_state = APP_AV_MEDIA_STATE_IDLE; +static int s_intv_cnt = 0; +static uint32_t s_pkt_cnt = 0; + +static TimerHandle_t s_tmr; + +static char *bda2str(esp_bd_addr_t bda, char *str, size_t size) +{ + if (bda == NULL || str == NULL || size < 18) { + return NULL; + } + + uint8_t *p = bda; + sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", + p[0], p[1], p[2], p[3], p[4], p[5]); + return str; +} +void hal_bluetooth_init(log_level level) +{ + + /* + * Bluetooth audio source init Start + */ + loglevel = level; + //running_test = false; + 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 (esp_bt_controller_init(&bt_cfg) != ESP_OK) { + LOG_ERROR("%s initialize controller failed\n", __func__); + return; + } + + if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) { + LOG_ERROR("%s enable controller failed\n", __func__); + return; + } + + if (esp_bluedroid_init() != ESP_OK) { + LOG_ERROR("%s initialize bluedroid failed\n", __func__); + return; + } + + if (esp_bluedroid_enable() != ESP_OK) { + LOG_ERROR("%s enable bluedroid failed\n", __func__); + 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 variable pin, input pin code when pairing + */ + esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE; + esp_bt_pin_code_t pin_code; + esp_bt_gap_set_pin(pin_type, 0, pin_code); + +} + +static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len) +{ + uint8_t *rmt_bdname = NULL; + uint8_t rmt_bdname_len = 0; + + if (!eir) { + return false; + } + + rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len); + if (!rmt_bdname) { + rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len); + } + + if (rmt_bdname) { + if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) { + rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN; + } + + if (bdname) { + memcpy(bdname, rmt_bdname, rmt_bdname_len); + bdname[rmt_bdname_len] = '\0'; + } + if (bdname_len) { + *bdname_len = rmt_bdname_len; + } + return true; + } + + return false; +} +#define LOG_INFO_NO_LF(fmt, ...) if (loglevel >= lINFO) logprint(fmt, ##__VA_ARGS__) +static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param) +{ + char bda_str[18]; + uint32_t cod = 0; + int32_t rssi = -129; /* invalid value */ + uint8_t *eir = NULL; + uint8_t nameLen = 0; + esp_bt_gap_dev_prop_t *p; + memset(s_peer_bdname, 0x00,sizeof(s_peer_bdname)); + + LOG_INFO("\n=======================\nScanned device: %s", bda2str(param->disc_res.bda, bda_str, 18)); + for (int i = 0; i < param->disc_res.num_prop; i++) { + p = param->disc_res.prop + i; + switch (p->type) { + case ESP_BT_GAP_DEV_PROP_COD: + cod = *(uint32_t *)(p->val); + LOG_INFO_NO_LF("\n-- Class of Device: 0x%x", cod); + break; + case ESP_BT_GAP_DEV_PROP_RSSI: + rssi = *(int8_t *)(p->val); + LOG_INFO_NO_LF("\n-- RSSI: %d", rssi); + break; + case ESP_BT_GAP_DEV_PROP_EIR: + eir = (uint8_t *)(p->val); + LOG_INFO_NO_LF("\n-- EIR: %d", eir); + break; + case ESP_BT_GAP_DEV_PROP_BDNAME: + nameLen = (p->len > ESP_BT_GAP_MAX_BDNAME_LEN) ? ESP_BT_GAP_MAX_BDNAME_LEN : (uint8_t)p->len; + memcpy(s_peer_bdname, (uint8_t *)(p->val), nameLen); + s_peer_bdname[nameLen] = '\0'; + LOG_INFO_NO_LF("\n-- Name: %s", s_peer_bdname); + break; + default: + break; + } + } + if (!esp_bt_gap_is_valid_cod(cod)){ + /* search for device with MAJOR service class as "rendering" in COD */ + LOG_INFO_NO_LF("\n--Invalid class of device. Skipping.\n"); + return; + } + else if (!(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) + { + LOG_INFO_NO_LF("\n--Not a rendering device. Skipping.\n"); + return; + } + + + /* search for device named "ESP_SPEAKER" in its extended inqury response */ + if (eir) { + LOG_INFO_NO_LF("\n--Getting details from eir.\n"); + get_name_from_eir(eir, s_peer_bdname, NULL); + LOG_INFO("--\nDevice name is %s",s_peer_bdname); + } + + if (strcmp((char *)s_peer_bdname, CONFIG_A2DP_SINK_NAME) == 0) { + LOG_INFO("Found a target device, address %s, name %s", bda_str, s_peer_bdname); + + if(esp_bt_gap_cancel_discovery()!=ESP_ERR_INVALID_STATE) + { + LOG_INFO("Cancel device discovery ..."); + memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN); + s_a2d_state = APP_AV_STATE_DISCOVERED; + } + else + { + LOG_ERROR("Cancel device discovery failed..."); + } + } + else + { + LOG_INFO("Not the device we are looking for. Continuing scan."); + } +} + + +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_DISC_RES_EVT: { + filter_inquiry_scan_result(param); + break; + } + case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: { + if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) + { + if (s_a2d_state == APP_AV_STATE_DISCOVERED) + { + if(esp_a2d_source_connect(s_peer_bda)!=ESP_ERR_INVALID_STATE) + { + s_a2d_state = APP_AV_STATE_CONNECTING; + LOG_INFO("Device discovery stopped. a2dp connecting to peer: %s", s_peer_bdname); + A2DP_TIMER_INIT; + } + else + { + // not discovered, continue to discover + LOG_INFO("Attempt at connecting failed, resuming discover..."); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + } + } + else + { + // not discovered, continue to discover + LOG_INFO("Device discovery failed, continue to discover..."); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + } + } + else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) { + LOG_INFO("Discovery started."); + } + else + { + LOG_DEBUG("This shouldn't happen. Discovery has only 2 states (for now)."); + } + break; + } + case ESP_BT_GAP_RMT_SRVCS_EVT: + LOG_DEBUG_EVENT(ESP_BT_GAP_RMT_SRVCS_EVT); + break; + case ESP_BT_GAP_RMT_SRVC_REC_EVT: + LOG_DEBUG_EVENT(ESP_BT_GAP_RMT_SRVC_REC_EVT); + break; + case ESP_BT_GAP_AUTH_CMPL_EVT: { + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + LOG_INFO("authentication success: %s", param->auth_cmpl.device_name); + //esp_log_buffer_hex(param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + LOG_ERROR("authentication failed, status:%d", param->auth_cmpl.stat); + } + break; + } + case ESP_BT_GAP_PIN_REQ_EVT: { + LOG_INFO("ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit); + if (param->pin_req.min_16_digit) { + LOG_INFO("Input pin code: 0000 0000 0000 0000"); + esp_bt_pin_code_t pin_code = {0}; + esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code); + } else { + LOG_INFO("Input pin code: 1234"); + 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_pin_reply(param->pin_req.bda, true, 4, pin_code); + } + break; + } + +#if (CONFIG_BT_SSP_ENABLED == true) + case ESP_BT_GAP_CFM_REQ_EVT: + LOG_INFO("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: + LOG_INFO("ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + LOG_INFO("ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + default: { + LOG_INFO("event: %d", event); + break; + } + } + return; +} + +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + + switch (event) { + case BT_APP_EVT_STACK_UP: { + LOG_INFO("BT Stack going up."); + /* set up device name */ + char *dev_name = CONFIG_A2DP_DEV_NAME; + esp_bt_dev_set_device_name(dev_name); + LOG_INFO("Preparing to connect to device: %s",CONFIG_A2DP_SINK_NAME); + + /* register GAP callback function */ + esp_bt_gap_register_callback(bt_app_gap_cb); + + /* initialize A2DP source */ + esp_a2d_register_callback(&bt_app_a2d_cb); + esp_a2d_source_register_data_callback(bt_app_a2d_data_cb); + esp_a2d_source_init(); + + /* set discoverable and connectable mode */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + + /* start device discovery */ + LOG_INFO("Starting device discovery..."); + s_a2d_state = APP_AV_STATE_DISCOVERING; + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + + /* create and start heart beat timer */ + do { + int tmr_id = 0; + s_tmr = xTimerCreate("connTmr", (CONFIG_A2DP_CONTROL_DELAY_MS / portTICK_RATE_MS), + pdTRUE, (void *)tmr_id, a2d_app_heart_beat); + xTimerStart(s_tmr, portMAX_DELAY); + } while (0); + break; + } + default: + LOG_ERROR("%s unhandled evt %d", __func__, event); + break; + } +} + +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); +} + +static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len) +{ + frames_t frames; + static int count = 0; + + DECLARE_ALL_MIN_MAX; + + if (len < 0 || data == NULL ) { + return 0; + } + + // bail out if A2DP isn't connected + LOCK; +// if(s_media_state != APP_AV_MEDIA_STATE_STARTED) +// { +// UNLOCK; +// return 0; +// } + + +// +///* Normally, we would want BT to not call us back unless we are not in BUFFERING state. +// That requires BT to not start until we are > OUTPUT_BUFFER +// // come back later, we are buffering (or stopped, need to handle that case ...) but we don't want silence */ +// if (output.state == OUTPUT_BUFFER) { +// UNLOCK; +// int32_t silence_bytes = (len >MAX_SILENCE_FRAMES * BYTES_PER_FRAME?MAX_SILENCE_FRAMES * BYTES_PER_FRAME:len; +// memcpy(bt_optr, (u8_t *)silencebuf, silence_bytes); +// return actual_len; +// } +// This is how the BTC layer calculates the number of bytes to +// for us to send. (BTC_SBC_DEC_PCM_DATA_LEN * sizeof(OI_INT16) - availPcmBytes + frames = len / BYTES_PER_FRAME; + output.device_frames = 0; + output.updated = gettime_ms(); + output.frames_played_dmp = output.frames_played; + //if (output.threshold < 20) output.threshold = 20; + int ret; + + frames_t wanted_frames=len/BYTES_PER_FRAME; + bt_optr = data; // needed for the _write_frames callback + do { + frames = _output_frames(wanted_frames); + wanted_frames -= frames; + } while (wanted_frames > 0 && frames != 0); + + if (wanted_frames > 0) { + LOG_DEBUG("need to pad with silence"); + memset(bt_optr, 0, wanted_frames * BYTES_PER_FRAME); + } + + + UNLOCK; + + SET_MIN_MAX(_buf_used(outputbuf),o); + SET_MIN_MAX(_buf_used(streambuf),s); + SET_MIN_MAX(frames,req); + + if (!(count++ & 0x1ff)) { + LOG_INFO( "count:%d" + "\n ----------+----------+-----------+ +----------+----------+----------------+" + "\n max | min | current| | max | min | current |" + "\n (ms) | (ms) | (ms)| | (frames) | (frames) | (frames)|" + "\n ----------+----------+-----------+ +----------+----------+----------------+" + "\nout %10d|%10d|%11d|" " |%10d|%10d|%16d|" + "\nstream %10d|%10d|%11d|" " |%10d|%10d|%16d|" + "\nN/A %10d|%10d|%11d|" " |%10d|%10d|%16d|" + "\nrequested %10d|%10d|%11d|" " |%10d|%10d|%16d|" + "\n ----------+----------+-----------+ +----------+----------+----------------+", + count, + BYTES_TO_MS(max_o), BYTES_TO_MS(min_o),BYTES_TO_MS(o),max_o,min_o,o, + BYTES_TO_MS(max_s), BYTES_TO_MS(min_s),BYTES_TO_MS(s),max_s,min_s,s, + BYTES_TO_MS(max_d),BYTES_TO_MS(min_d),BYTES_TO_MS(d),max_d,min_d,d, + FRAMES_TO_MS(req),FRAMES_TO_MS(req),FRAMES_TO_MS(req), req, req,req); + RESET_ALL_MIN_MAX; + } + + return frames * BYTES_PER_FRAME; +} +static bool running_test; +#ifdef BTAUDIO +bool test_open(const char *device, unsigned rates[], bool userdef_rates) { + +// running_test = true; +// while(running_test) +// { +// // wait until BT playback has started +// // this will allow querying the sample rate +// usleep(100000); +// } + + memset(rates, 0, MAX_SUPPORTED_SAMPLERATES * sizeof(unsigned)); + if (!strcmp(device, "BT")) { + rates[0] = 44100; + } else { + unsigned _rates[] = { 96000, 88200, 48000, 44100, 32000, 0 }; + memcpy(rates, _rates, sizeof(_rates)); + } + return true; +} +#endif +static void a2d_app_heart_beat(void *arg) +{ + bt_app_work_dispatch(bt_app_av_sm_hdlr, BT_APP_HEART_BEAT_EVT, NULL, 0, NULL); +} + +static void bt_app_av_sm_hdlr(uint16_t event, void *param) +{ + //LOG_DEBUG("%s state %s, evt 0x%x, output state: %d", __func__, APP_AV_STATE_DESC[s_a2d_state], event, output.state); + switch (s_a2d_state) { + case APP_AV_STATE_DISCOVERING: + LOG_DEBUG("state %s, evt 0x%x, output state: %d", APP_AV_STATE_DESC[s_a2d_state], event, output.state); + break; + case APP_AV_STATE_DISCOVERED: + LOG_DEBUG("state %s, evt 0x%x, output state: %d", APP_AV_STATE_DESC[s_a2d_state], event, output.state); + break; + case APP_AV_STATE_UNCONNECTED: + bt_app_av_state_unconnected(event, param); + break; + case APP_AV_STATE_CONNECTING: + bt_app_av_state_connecting(event, param); + break; + case APP_AV_STATE_CONNECTED: + bt_app_av_state_connected(event, param); + break; + case APP_AV_STATE_DISCONNECTING: + bt_app_av_state_disconnecting(event, param); + break; + default: + LOG_ERROR("%s invalid state %d", __func__, s_a2d_state); + break; + } +} + +static void bt_app_av_state_unconnected(uint16_t event, void *param) +{ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: + LOG_DEBUG_EVENT(ESP_A2D_CONNECTION_STATE_EVT); + break; + case ESP_A2D_AUDIO_STATE_EVT: + LOG_DEBUG_EVENT(ESP_A2D_AUDIO_STATE_EVT); + break; + case ESP_A2D_AUDIO_CFG_EVT: + LOG_DEBUG_EVENT(ESP_A2D_AUDIO_CFG_EVT); + break; + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + LOG_DEBUG_EVENT(ESP_A2D_MEDIA_CTRL_ACK_EVT); + break; + case BT_APP_HEART_BEAT_EVT: { + uint8_t *p = s_peer_bda; + LOG_INFO("BT_APP_HEART_BEAT_EVT a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x",p[0], p[1], p[2], p[3], p[4], p[5]); + switch (esp_bluedroid_get_status()) { + case ESP_BLUEDROID_STATUS_UNINITIALIZED: + LOG_INFO("BlueDroid Status is ESP_BLUEDROID_STATUS_UNINITIALIZED."); + break; + case ESP_BLUEDROID_STATUS_INITIALIZED: + LOG_INFO("BlueDroid Status is ESP_BLUEDROID_STATUS_INITIALIZED."); + break; + case ESP_BLUEDROID_STATUS_ENABLED: + LOG_INFO("BlueDroid Status is ESP_BLUEDROID_STATUS_ENABLED."); + break; + default: + break; + } + if(esp_a2d_source_connect(s_peer_bda)!=ESP_ERR_INVALID_STATE) + { + s_a2d_state = APP_AV_STATE_CONNECTING; + LOG_INFO("a2dp connecting to peer: %s", s_peer_bdname); + A2DP_TIMER_INIT; + } + else + { + // not discovered, continue to discover + LOG_INFO("Attempt at connecting failed, resuming discover..."); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + } + break; + } + default: + LOG_ERROR("%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_av_state_connecting(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) { + LOG_INFO("a2dp connected! Stopping scan. "); + s_a2d_state = APP_AV_STATE_CONNECTED; + s_media_state = APP_AV_MEDIA_STATE_IDLE; + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + s_a2d_state = APP_AV_STATE_UNCONNECTED; + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: + LOG_DEBUG_EVENT(ESP_A2D_AUDIO_STATE_EVT); + break; + case ESP_A2D_AUDIO_CFG_EVT: + LOG_DEBUG_EVENT(ESP_A2D_AUDIO_CFG_EVT); + break; + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + LOG_DEBUG_EVENT(ESP_A2D_MEDIA_CTRL_ACK_EVT); + break; + case BT_APP_HEART_BEAT_EVT: + if (IS_A2DP_TIMER_OVER) + { + s_a2d_state = APP_AV_STATE_UNCONNECTED; + LOG_DEBUG("Connect timed out. Setting state to Unconnected. "); + } + LOG_SDEBUG("BT_APP_HEART_BEAT_EVT"); + break; + default: + LOG_ERROR("%s unhandled evt %d", __func__, event); + break; + } +} + + +static void bt_app_av_media_proc(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (s_media_state) { + case APP_AV_MEDIA_STATE_IDLE: { + if (event == BT_APP_HEART_BEAT_EVT) { + if(output.state < OUTPUT_STOPPED ) + { + // TODO: anything to do while we are waiting? Should we check if we're still connected? + } + else if(output.state >= OUTPUT_BUFFER ) + { + LOG_INFO("buffering output, a2dp media ready and connected. Starting checking if ready..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY); + } +// else if(running_test) +// { +// LOG_INFO("buffering output, a2dp media ready and connected. Starting checking if ready..."); +// +// esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY); +// } + + + } else if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS + ) { + LOG_INFO("a2dp media ready, starting media playback ..."); + s_media_state = APP_AV_MEDIA_STATE_STARTING; + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START); + + } + } + break; + } + case APP_AV_MEDIA_STATE_STARTING: { + if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + LOG_INFO("a2dp media started successfully."); + s_intv_cnt = 0; + s_media_state = APP_AV_MEDIA_STATE_STARTED; + } else { + // not started succesfully, transfer to idle state + LOG_INFO("a2dp media start failed."); + s_media_state = APP_AV_MEDIA_STATE_IDLE; + } + } + break; + } + case APP_AV_MEDIA_STATE_STARTED: { + if (event == BT_APP_HEART_BEAT_EVT) { + if(output.state <= OUTPUT_STOPPED) { + LOG_INFO("Output state is stopped. Stopping a2dp media ..."); + s_media_state = APP_AV_MEDIA_STATE_STOPPING; + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP); + + s_intv_cnt = 0; + } + } + break; + } + case APP_AV_MEDIA_STATE_STOPPING: { + LOG_DEBUG_EVENT(APP_AV_MEDIA_STATE_STOPPING); + if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + LOG_INFO("a2dp media stopped successfully..."); + //s_media_state = APP_AV_MEDIA_STATE_WAIT_DISCONNECT; + + s_media_state = APP_AV_MEDIA_STATE_IDLE; + // todo: should we disconnect? +// esp_a2d_source_disconnect(s_peer_bda); +// s_a2d_state = APP_AV_STATE_DISCONNECTING; + } else { + LOG_INFO("a2dp media stopping..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP); + } + } + break; + } + } +} + +static void bt_app_av_state_connected(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + LOG_INFO("a2dp disconnected"); + s_a2d_state = APP_AV_STATE_UNCONNECTED; + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: { + LOG_DEBUG_EVENT(ESP_A2D_AUDIO_STATE_EVT); + a2d = (esp_a2d_cb_param_t *)(param); + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { + s_pkt_cnt = 0; + } + break; + } + case ESP_A2D_AUDIO_CFG_EVT: + // not suppposed to occur for A2DP source + LOG_DEBUG_EVENT(ESP_A2D_AUDIO_CFG_EVT); + break; + case ESP_A2D_MEDIA_CTRL_ACK_EVT:{ + LOG_DEBUG_EVENT(ESP_A2D_MEDIA_CTRL_ACK_EVT); + bt_app_av_media_proc(event, param); + break; + } + case BT_APP_HEART_BEAT_EVT: { + LOG_SDEBUG_EVENT(BT_APP_HEART_BEAT_EVT); + bt_app_av_media_proc(event, param); + break; + } + default: + LOG_ERROR("%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_av_state_disconnecting(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + LOG_DEBUG_EVENT(ESP_A2D_CONNECTION_STATE_EVT); + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + LOG_INFO("a2dp disconnected"); + s_a2d_state = APP_AV_STATE_UNCONNECTED; + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: + LOG_DEBUG_EVENT(ESP_A2D_AUDIO_STATE_EVT); + break; + case ESP_A2D_AUDIO_CFG_EVT: + LOG_DEBUG_EVENT(ESP_A2D_AUDIO_CFG_EVT); + break; + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + LOG_DEBUG_EVENT(ESP_A2D_MEDIA_CTRL_ACK_EVT); + break; + case BT_APP_HEART_BEAT_EVT: + LOG_DEBUG_EVENT(BT_APP_HEART_BEAT_EVT); + break; + default: + LOG_ERROR("%s unhandled evt %d", __func__, event); + break; + } +} diff --git a/main/esp_app_main.c b/main/esp_app_main.c index ca798b21..b9f2dec1 100644 --- a/main/esp_app_main.c +++ b/main/esp_app_main.c @@ -132,6 +132,10 @@ void app_main() "decode=" CONFIG_LOGGING_DECODE, "-d", "output=" CONFIG_LOGGING_OUTPUT, +#ifdef CONFIG_LOG_OPTION + "-d", + CONFIG_LOG_OPTION, +#endif "-b", "500:2000" diff --git a/main/main.c b/main/main.c index 8be1dd37..688c5c29 100644 --- a/main/main.c +++ b/main/main.c @@ -525,7 +525,7 @@ int main(int argc, char **argv) { pidfile = optarg; break; #endif -#ifndef DACAUDIO +#if !DACAUDIO && !BTAUDIO case 'l': list_devices(); exit(0); @@ -749,7 +749,9 @@ int main(int argc, char **argv) { stream_init(log_stream, stream_buf_size); -#if DACAUDIO +#if BTAUDIO + output_init_bt(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle); +#elif DACAUDIO output_init_dac(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle); #else if (!strcmp(output_device, "-")) { @@ -801,6 +803,8 @@ int main(int argc, char **argv) { #if DACAUDIO output_close_dac(); +#elif BTAUDIO + output_close_bt(); #else if (!strcmp(output_device, "-")) { output_close_stdout(); diff --git a/main/output_bt.c b/main/output_bt.c index 3bfeccf6..869caccb 100644 --- a/main/output_bt.c +++ b/main/output_bt.c @@ -1,28 +1,6 @@ #include "squeezelite.h" -#include -#include -#include -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/timers.h" -#include "nvs.h" -#include "nvs_flash.h" -#include "esp_system.h" -#include "esp_log.h" -#include "esp_bt.h" -#include "bt_app_core.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" - -#define BT_AV_TAG "BT_AV" - static log_level loglevel; static bool running = true; @@ -38,14 +16,13 @@ extern struct buffer *streambuf; #define FRAME_BLOCK MAX_SILENCE_FRAMES extern u8_t *silencebuf; -static u8_t *optr; -int64_t connecting_timeout = 0; -#define A2DP_TIMER_INIT connecting_timeout = esp_timer_get_time() +(CONFIG_A2DP_CONNECT_TIMEOUT_MS * 1000) -#define IS_A2DP_TIMER_OVER esp_timer_get_time() >= connecting_timeout + +extern u8_t *bt_optr; +void hal_bluetooth_init(log_level); static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr); - +#if BTAUDIO void set_volume(unsigned left, unsigned right) { LOG_DEBUG("setting internal gain left: %u right: %u", left, right); LOCK; @@ -53,99 +30,17 @@ void set_volume(unsigned left, unsigned right) { output.gainR = right; UNLOCK; } -#define LOG_DEBUG_EVENT(e) LOG_DEBUG("evt: " STR(e)) -#define LOG_SDEBUG_EVENT(e) LOG_SDEBUG("evt: " STR(e)) - -/* event for handler "bt_av_hdl_stack_up */ -enum { - BT_APP_EVT_STACK_UP = 0, -}; - -/* A2DP global state */ -enum { - APP_AV_STATE_IDLE, - APP_AV_STATE_DISCOVERING, - APP_AV_STATE_DISCOVERED, - APP_AV_STATE_UNCONNECTED, - APP_AV_STATE_CONNECTING, - APP_AV_STATE_CONNECTED, - APP_AV_STATE_DISCONNECTING, -}; - -char * APP_AV_STATE_DESC[] = { - "APP_AV_STATE_IDLE", - "APP_AV_STATE_DISCOVERING", - "APP_AV_STATE_DISCOVERED", - "APP_AV_STATE_UNCONNECTED", - "APP_AV_STATE_CONNECTING", - "APP_AV_STATE_CONNECTED", - "APP_AV_STATE_DISCONNECTING" -}; +#endif - -/* sub states of APP_AV_STATE_CONNECTED */ - -enum { - APP_AV_MEDIA_STATE_IDLE, - APP_AV_MEDIA_STATE_STARTING, - APP_AV_MEDIA_STATE_STARTED, - APP_AV_MEDIA_STATE_STOPPING, - APP_AV_MEDIA_STATE_WAIT_DISCONNECT -}; - -#define BT_APP_HEART_BEAT_EVT (0xff00) - -/// handler for bluetooth stack enabled events -static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); - -/// callback function for A2DP source -static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); - -/// callback function for A2DP source audio data stream -static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len); - -static void a2d_app_heart_beat(void *arg); - -/// A2DP application state machine -static void bt_app_av_sm_hdlr(uint16_t event, void *param); - -/* A2DP application state machine handler for each state */ -static void bt_app_av_state_unconnected(uint16_t event, void *param); -static void bt_app_av_state_connecting(uint16_t event, void *param); -static void bt_app_av_state_connected(uint16_t event, void *param); -static void bt_app_av_state_disconnecting(uint16_t event, void *param); - -static esp_bd_addr_t s_peer_bda = {0}; -static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; -static int s_a2d_state = APP_AV_STATE_IDLE; -static int s_media_state = APP_AV_MEDIA_STATE_IDLE; -static int s_intv_cnt = 0; -static uint32_t s_pkt_cnt = 0; - -static TimerHandle_t s_tmr; - -static char *bda2str(esp_bd_addr_t bda, char *str, size_t size) -{ - if (bda == NULL || str == NULL || size < 18) { - return NULL; - } - - uint8_t *p = bda; - sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", - p[0], p[1], p[2], p[3], p[4], p[5]); - return str; -} - - -void output_init_dac(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { +void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { loglevel = level; LOG_INFO("init output BT"); memset(&output, 0, sizeof(output)); - output.start_frames = 0; //CONFIG_ //FRAME_BLOCK * 2; + output.start_frames = FRAME_BLOCK; //CONFIG_ //FRAME_BLOCK * 2; output.write_cb = &_write_frames; output.rate_delay = rate_delay; @@ -153,77 +48,18 @@ void output_init_dac(log_level level, char *device, unsigned output_buf_size, ch if (!rates[0]) { rates[0] = 44100; } - /* - * Bluetooth audio source init Start - */ - bt_set_log_level(level); - 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 (esp_bt_controller_init(&bt_cfg) != ESP_OK) { - LOG_ERROR("%s initialize controller failed\n", __func__); - return; - } - - if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) { - LOG_ERROR("%s enable controller failed\n", __func__); - return; - } - - if (esp_bluedroid_init() != ESP_OK) { - LOG_ERROR("%s initialize bluedroid failed\n", __func__); - return; - } - - if (esp_bluedroid_enable() != ESP_OK) { - LOG_ERROR("%s enable bluedroid failed\n", __func__); - 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 variable pin, input pin code when pairing - */ - esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE; - esp_bt_pin_code_t pin_code; - esp_bt_gap_set_pin(pin_type, 0, pin_code); - + hal_bluetooth_init(loglevel); /* * Bluetooth audio source init Start */ device = "BT"; output_init_common(level, device, output_buf_size, rates, idle); -//#if LINUX || OSX || FREEBSD || POSIX -// pthread_attr_t attr; -// pthread_attr_init(&attr); -//#ifdef PTHREAD_STACK_MIN -// pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); -//#endif -// pthread_create(&thread, &attr, output_thread, NULL); -// pthread_attr_destroy(&attr); -//#endif -//#if WIN -// thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread, NULL, 0, NULL); -//#endif + } -void output_close_dac(void) { +void output_close_bt(void) { LOG_INFO("close output"); - LOCK; running = false; UNLOCK; @@ -233,671 +69,42 @@ void output_close_dac(void) { static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr) { - - if (!silence) { - + + if (!silence ) { + DEBUG_LOG_TIMED(200,"Not silence, Writing audio out."); + /* TODO need 16 bit fix + if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) { _apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr); } - + if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { _apply_gain(outputbuf, out_frames, gainL, gainR); } - -#if BYTES_PER_FRAME == 4 - memcpy(optr, outputbuf->readp, out_frames * BYTES_PER_FRAME); + +#if BYTES_PER_FRAME == 4 + memcpy(bt_optr, outputbuf->readp, out_frames * BYTES_PER_FRAME); #else - { + { frames_t count = out_frames; s32_t *_iptr = (s32_t*) outputbuf->readp; - s16_t *_optr = (s16_t*) optr; + s16_t *_optr = (s16_t*) bt_optr; while (count--) { *_optr++ = *_iptr++ >> 16; *_optr++ = *_iptr++ >> 16; } - } -#endif + } +#endif } else { + DEBUG_LOG_TIMED(200,"Silence flag true. Writing silence to audio out."); u8_t *buf = silencebuf; - memcpy(optr, buf, out_frames * 4); + memcpy(bt_optr, buf, out_frames * 4); } - - optr += out_frames * 4; + + bt_optr += out_frames * 4; return (int)out_frames; } - -//static void *output_thread() { -// -// -// while (running) { -// -// //nothing to do here, for now. Feeding the buffer is -// usleep(500000); -// continue; -// } -// -// output.device_frames = 0; -// output.updated = gettime_ms(); -// output.frames_played_dmp = output.frames_played; -// -// _output_frames(FRAME_BLOCK); -// -// UNLOCK; -// -// if (buffill) { -//// Do Stuff here -// usleep((buffill * 1000 * 1000) / output.current_sample_rate); -// buffill = 0; -// } else { -// usleep((FRAME_BLOCK * 1000 * 1000) / output.current_sample_rate); -// } -// -// } -// -// return 0; -//} - - - -static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len) -{ - uint8_t *rmt_bdname = NULL; - uint8_t rmt_bdname_len = 0; - - if (!eir) { - return false; - } - - rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len); - if (!rmt_bdname) { - rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len); - } - - if (rmt_bdname) { - if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) { - rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN; - } - - if (bdname) { - memcpy(bdname, rmt_bdname, rmt_bdname_len); - bdname[rmt_bdname_len] = '\0'; - } - if (bdname_len) { - *bdname_len = rmt_bdname_len; - } - return true; - } - - return false; -} -#define LOG_INFO_NO_LF(fmt, ...) if (loglevel >= lINFO) logprint(fmt, ##__VA_ARGS__) -static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param) -{ - char bda_str[18]; - uint32_t cod = 0; - int32_t rssi = -129; /* invalid value */ - uint8_t *eir = NULL; - uint8_t nameLen = 0; - esp_bt_gap_dev_prop_t *p; - memset(s_peer_bdname, 0x00,sizeof(s_peer_bdname)); - - LOG_INFO("\n=======================\nScanned device: %s", bda2str(param->disc_res.bda, bda_str, 18)); - for (int i = 0; i < param->disc_res.num_prop; i++) { - p = param->disc_res.prop + i; - switch (p->type) { - case ESP_BT_GAP_DEV_PROP_COD: - cod = *(uint32_t *)(p->val); - LOG_INFO_NO_LF("\n-- Class of Device: 0x%x", cod); - break; - case ESP_BT_GAP_DEV_PROP_RSSI: - rssi = *(int8_t *)(p->val); - LOG_INFO_NO_LF("\n-- RSSI: %d", rssi); - break; - case ESP_BT_GAP_DEV_PROP_EIR: - eir = (uint8_t *)(p->val); - LOG_INFO_NO_LF("\n-- EIR: %d", eir); - break; - case ESP_BT_GAP_DEV_PROP_BDNAME: - nameLen = (p->len > ESP_BT_GAP_MAX_BDNAME_LEN) ? ESP_BT_GAP_MAX_BDNAME_LEN : (uint8_t)p->len; - memcpy(s_peer_bdname, (uint8_t *)(p->val), nameLen); - s_peer_bdname[nameLen] = '\0'; - LOG_INFO_NO_LF("\n-- Name: %s", s_peer_bdname); - break; - default: - break; - } - } - if (!esp_bt_gap_is_valid_cod(cod)){ - /* search for device with MAJOR service class as "rendering" in COD */ - LOG_INFO_NO_LF("\n--Invalid class of device. Skipping.\n"); - return; - } - else if (!(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) - { - LOG_INFO_NO_LF("\n--Not a rendering device. Skipping.\n"); - return; - } - - - /* search for device named "ESP_SPEAKER" in its extended inqury response */ - if (eir) { - LOG_INFO_NO_LF("\n--Getting details from eir.\n"); - get_name_from_eir(eir, s_peer_bdname, NULL); - LOG_INFO("--\nDevice name is %s",s_peer_bdname); - } - - if (strcmp((char *)s_peer_bdname, CONFIG_A2DP_SINK_NAME) == 0) { - LOG_INFO("Found a target device, address %s, name %s", bda_str, s_peer_bdname); - - if(esp_bt_gap_cancel_discovery()!=ESP_ERR_INVALID_STATE) - { - LOG_INFO("Cancel device discovery ..."); - memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN); - s_a2d_state = APP_AV_STATE_DISCOVERED; - } - else - { - LOG_ERROR("Cancel device discovery failed..."); - } - } - else - { - LOG_INFO("Not the device we are looking for. Continuing scan."); - } -} - - -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_DISC_RES_EVT: { - filter_inquiry_scan_result(param); - break; - } - case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: { - if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) - { - if (s_a2d_state == APP_AV_STATE_DISCOVERED) - { - if(esp_a2d_source_connect(s_peer_bda)!=ESP_ERR_INVALID_STATE) - { - s_a2d_state = APP_AV_STATE_CONNECTING; - LOG_INFO("Device discovery stopped. a2dp connecting to peer: %s", s_peer_bdname); - A2DP_TIMER_INIT; - } - else - { - // not discovered, continue to discover - LOG_INFO("Attempt at connecting failed, resuming discover..."); - esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); - } - } - else - { - // not discovered, continue to discover - LOG_INFO("Device discovery failed, continue to discover..."); - esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); - } - } - else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) { - LOG_INFO("Discovery started."); - } - else - { - LOG_DEBUG("This shouldn't happen. Discovery has only 2 states (for now)."); - } - break; - } - case ESP_BT_GAP_RMT_SRVCS_EVT: - LOG_DEBUG_EVENT(ESP_BT_GAP_RMT_SRVCS_EVT); - break; - case ESP_BT_GAP_RMT_SRVC_REC_EVT: - LOG_DEBUG_EVENT(ESP_BT_GAP_RMT_SRVC_REC_EVT); - break; - case ESP_BT_GAP_AUTH_CMPL_EVT: { - if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { - LOG_INFO("authentication success: %s", param->auth_cmpl.device_name); - //esp_log_buffer_hex(param->auth_cmpl.bda, ESP_BD_ADDR_LEN); - } else { - LOG_ERROR("authentication failed, status:%d", param->auth_cmpl.stat); - } - break; - } - case ESP_BT_GAP_PIN_REQ_EVT: { - LOG_INFO("ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit); - if (param->pin_req.min_16_digit) { - LOG_INFO("Input pin code: 0000 0000 0000 0000"); - esp_bt_pin_code_t pin_code = {0}; - esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code); - } else { - LOG_INFO("Input pin code: 1234"); - 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_pin_reply(param->pin_req.bda, true, 4, pin_code); - } - break; - } - -#if (CONFIG_BT_SSP_ENABLED == true) - case ESP_BT_GAP_CFM_REQ_EVT: - LOG_INFO("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: - LOG_INFO("ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); - break; - LOG_INFO("ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); - break; -#endif - - default: { - LOG_INFO("event: %d", event); - break; - } - } - return; -} - -static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) -{ - - switch (event) { - case BT_APP_EVT_STACK_UP: { - LOG_INFO("BT Stack going up."); - /* set up device name */ - char *dev_name = CONFIG_A2DP_DEV_NAME; - esp_bt_dev_set_device_name(dev_name); - LOG_INFO("Preparing to connect to device: %s",CONFIG_A2DP_SINK_NAME); - - /* register GAP callback function */ - esp_bt_gap_register_callback(bt_app_gap_cb); - - /* initialize A2DP source */ - esp_a2d_register_callback(&bt_app_a2d_cb); - esp_a2d_source_register_data_callback(bt_app_a2d_data_cb); - esp_a2d_source_init(); - - /* set discoverable and connectable mode */ - esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); - - /* start device discovery */ - LOG_INFO("Starting device discovery..."); - s_a2d_state = APP_AV_STATE_DISCOVERING; - esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); - - /* create and start heart beat timer */ - do { - int tmr_id = 0; - s_tmr = xTimerCreate("connTmr", (CONFIG_A2DP_CONTROL_DELAY_MS / portTICK_RATE_MS), - pdTRUE, (void *)tmr_id, a2d_app_heart_beat); - xTimerStart(s_tmr, portMAX_DELAY); - } while (0); - break; - } - default: - LOG_ERROR("%s unhandled evt %d", __func__, event); - break; - } -} - -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); -} - -static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len) -{ - frames_t frames; - static int count = 0; - static unsigned min_o = -1, max_o = 0, min_s = -1, max_s = 0; - unsigned o, s; - - - if (len < 0 || data == NULL ) { - return 0; - } - // bail out if A2DP isn't connected - LOCK; - if(s_media_state != APP_AV_MEDIA_STATE_STARTED) - { - UNLOCK; - return 0; - } - - -// todo: fix me!! -/* Normally, we would want BT to not call us back unless we are not in BUFFERING state. - That requires BT to not start until we are > OUTPUT_BUFFER - // come back later, we are buffering (or stopped, need to handle that case ...) but we don't want silence */ -// if (output.state <= OUTPUT_BUFFER) { -// UNLOCK; -// return 0; -// } - - - frames = len / 4; - output.device_frames = 0; - output.updated = gettime_ms(); - output.frames_played_dmp = output.frames_played; - if (output.threshold < 20) output.threshold = 20; - - optr = data; - frames = _output_frames(frames); - - UNLOCK; - - o = _buf_used(outputbuf); - if (o < min_o) min_o = o; - if (o > max_o) max_o = o; - - s = _buf_used(streambuf); - if (s < min_s) min_s = s; - if (s > max_s) max_s = s; - - if (!(count++ & 0x7ff)) { - LOG_INFO("output:%d/%d/%d stream:%d/%d/%d (max/min/current)", max_o, min_o, o, max_s, min_s, s); - min_o = min_s = -1; - max_o = max_s = -0; - } - - return frames * 4; -} - -bool test_open(const char *device, unsigned rates[], bool userdef_rates) { - memset(rates, 0, MAX_SUPPORTED_SAMPLERATES * sizeof(unsigned)); - if (!strcmp(device, "BT")) { - unsigned _rates[] = { 48000, 44100, 0 }; - memcpy(rates, _rates, sizeof(_rates)); - } else { - unsigned _rates[] = { 96000, 88200, 48000, 44100, 32000, 0 }; - memcpy(rates, _rates, sizeof(_rates)); - } - return true; -} - -static void a2d_app_heart_beat(void *arg) -{ - bt_app_work_dispatch(bt_app_av_sm_hdlr, BT_APP_HEART_BEAT_EVT, NULL, 0, NULL); -} - -static void bt_app_av_sm_hdlr(uint16_t event, void *param) -{ - //LOG_DEBUG("%s state %s, evt 0x%x, output state: %d", __func__, APP_AV_STATE_DESC[s_a2d_state], event, output.state); - switch (s_a2d_state) { - case APP_AV_STATE_DISCOVERING: - LOG_DEBUG("state %s, evt 0x%x, output state: %d", APP_AV_STATE_DESC[s_a2d_state], event, output.state); - break; - case APP_AV_STATE_DISCOVERED: - LOG_DEBUG("state %s, evt 0x%x, output state: %d", APP_AV_STATE_DESC[s_a2d_state], event, output.state); - break; - case APP_AV_STATE_UNCONNECTED: - bt_app_av_state_unconnected(event, param); - break; - case APP_AV_STATE_CONNECTING: - bt_app_av_state_connecting(event, param); - break; - case APP_AV_STATE_CONNECTED: - bt_app_av_state_connected(event, param); - break; - case APP_AV_STATE_DISCONNECTING: - bt_app_av_state_disconnecting(event, param); - break; - default: - LOG_ERROR("%s invalid state %d", __func__, s_a2d_state); - break; - } -} - -static void bt_app_av_state_unconnected(uint16_t event, void *param) -{ - switch (event) { - case ESP_A2D_CONNECTION_STATE_EVT: - LOG_DEBUG_EVENT(ESP_A2D_CONNECTION_STATE_EVT); - break; - case ESP_A2D_AUDIO_STATE_EVT: - LOG_DEBUG_EVENT(ESP_A2D_AUDIO_STATE_EVT); - break; - case ESP_A2D_AUDIO_CFG_EVT: - LOG_DEBUG_EVENT(ESP_A2D_AUDIO_CFG_EVT); - break; - case ESP_A2D_MEDIA_CTRL_ACK_EVT: - LOG_DEBUG_EVENT(ESP_A2D_MEDIA_CTRL_ACK_EVT); - break; - case BT_APP_HEART_BEAT_EVT: { - uint8_t *p = s_peer_bda; - LOG_INFO("BT_APP_HEART_BEAT_EVT a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x",p[0], p[1], p[2], p[3], p[4], p[5]); - switch (esp_bluedroid_get_status()) { - case ESP_BLUEDROID_STATUS_UNINITIALIZED: - LOG_INFO("BlueDroid Status is ESP_BLUEDROID_STATUS_UNINITIALIZED."); - break; - case ESP_BLUEDROID_STATUS_INITIALIZED: - LOG_INFO("BlueDroid Status is ESP_BLUEDROID_STATUS_INITIALIZED."); - break; - case ESP_BLUEDROID_STATUS_ENABLED: - LOG_INFO("BlueDroid Status is ESP_BLUEDROID_STATUS_ENABLED."); - break; - default: - break; - } - if(esp_a2d_source_connect(s_peer_bda)!=ESP_ERR_INVALID_STATE) - { - s_a2d_state = APP_AV_STATE_CONNECTING; - LOG_INFO("a2dp connecting to peer: %s", s_peer_bdname); - A2DP_TIMER_INIT; - } - else - { - // not discovered, continue to discover - LOG_INFO("Attempt at connecting failed, resuming discover..."); - esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); - } - break; - } - default: - LOG_ERROR("%s unhandled evt %d", __func__, event); - break; - } -} - -static void bt_app_av_state_connecting(uint16_t event, void *param) -{ - esp_a2d_cb_param_t *a2d = NULL; - - switch (event) { - case ESP_A2D_CONNECTION_STATE_EVT: { - a2d = (esp_a2d_cb_param_t *)(param); - if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) { - LOG_INFO("a2dp connected! Stopping scan. "); - s_a2d_state = APP_AV_STATE_CONNECTED; - s_media_state = APP_AV_MEDIA_STATE_IDLE; - esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); - } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { - s_a2d_state = APP_AV_STATE_UNCONNECTED; - } - break; - } - case ESP_A2D_AUDIO_STATE_EVT: - LOG_DEBUG_EVENT(ESP_A2D_AUDIO_STATE_EVT); - break; - case ESP_A2D_AUDIO_CFG_EVT: - LOG_DEBUG_EVENT(ESP_A2D_AUDIO_CFG_EVT); - break; - case ESP_A2D_MEDIA_CTRL_ACK_EVT: - LOG_DEBUG_EVENT(ESP_A2D_MEDIA_CTRL_ACK_EVT); - break; - case BT_APP_HEART_BEAT_EVT: - if (IS_A2DP_TIMER_OVER) - { - s_a2d_state = APP_AV_STATE_UNCONNECTED; - LOG_DEBUG("Connect timed out. Setting state to Unconnected. "); - } - LOG_SDEBUG("BT_APP_HEART_BEAT_EVT"); - break; - default: - LOG_ERROR("%s unhandled evt %d", __func__, event); - break; - } -} - - -static void bt_app_av_media_proc(uint16_t event, void *param) -{ - esp_a2d_cb_param_t *a2d = NULL; - switch (s_media_state) { - case APP_AV_MEDIA_STATE_IDLE: { - if (event == BT_APP_HEART_BEAT_EVT) { - if(output.state <= OUTPUT_STOPPED ) - { - // TODO: anything to do while we are waiting? Should we check if we're still connected? - } - else if(output.state <= OUTPUT_BUFFER ) - { - LOG_INFO("buffering output, a2dp media ready and connected. Starting checking if ready..."); - esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY); - } - - - } else if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { - a2d = (esp_a2d_cb_param_t *)(param); - if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY && - a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS - ) { - LOG_INFO("a2dp media ready, starting media playback ..."); - s_media_state = APP_AV_MEDIA_STATE_STARTING; - esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START); - - } - } - break; - } - case APP_AV_MEDIA_STATE_STARTING: { - if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { - a2d = (esp_a2d_cb_param_t *)(param); - if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START && - a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { - LOG_INFO("a2dp media started successfully."); - s_intv_cnt = 0; - s_media_state = APP_AV_MEDIA_STATE_STARTED; - } else { - // not started succesfully, transfer to idle state - LOG_INFO("a2dp media start failed."); - s_media_state = APP_AV_MEDIA_STATE_IDLE; - } - } - break; - } - case APP_AV_MEDIA_STATE_STARTED: { - if (event == BT_APP_HEART_BEAT_EVT) { - if(output.state <= OUTPUT_STOPPED) { - LOG_INFO("Output state is stopped. Stopping a2dp media ..."); - s_media_state = APP_AV_MEDIA_STATE_STOPPING; - esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP); - - s_intv_cnt = 0; - } - } - break; - } - case APP_AV_MEDIA_STATE_STOPPING: { - LOG_DEBUG_EVENT(APP_AV_MEDIA_STATE_STOPPING); - if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { - a2d = (esp_a2d_cb_param_t *)(param); - if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP && - a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { - LOG_INFO("a2dp media stopped successfully..."); - //s_media_state = APP_AV_MEDIA_STATE_WAIT_DISCONNECT; - - s_media_state = APP_AV_MEDIA_STATE_IDLE; - // todo: should we disconnect? -// esp_a2d_source_disconnect(s_peer_bda); -// s_a2d_state = APP_AV_STATE_DISCONNECTING; - } else { - LOG_INFO("a2dp media stopping..."); - esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP); - } - } - break; - } - } -} - -static void bt_app_av_state_connected(uint16_t event, void *param) -{ - esp_a2d_cb_param_t *a2d = NULL; - switch (event) { - case ESP_A2D_CONNECTION_STATE_EVT: { - a2d = (esp_a2d_cb_param_t *)(param); - if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { - LOG_INFO("a2dp disconnected"); - s_a2d_state = APP_AV_STATE_UNCONNECTED; - esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); - } - break; - } - case ESP_A2D_AUDIO_STATE_EVT: { - LOG_DEBUG_EVENT(ESP_A2D_AUDIO_STATE_EVT); - a2d = (esp_a2d_cb_param_t *)(param); - if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { - s_pkt_cnt = 0; - } - break; - } - case ESP_A2D_AUDIO_CFG_EVT: - // not suppposed to occur for A2DP source - LOG_DEBUG_EVENT(ESP_A2D_AUDIO_CFG_EVT); - break; - case ESP_A2D_MEDIA_CTRL_ACK_EVT:{ - LOG_DEBUG_EVENT(ESP_A2D_MEDIA_CTRL_ACK_EVT); - bt_app_av_media_proc(event, param); - break; - } - case BT_APP_HEART_BEAT_EVT: { - LOG_SDEBUG_EVENT(BT_APP_HEART_BEAT_EVT); - bt_app_av_media_proc(event, param); - break; - } - default: - LOG_ERROR("%s unhandled evt %d", __func__, event); - break; - } -} - -static void bt_app_av_state_disconnecting(uint16_t event, void *param) -{ - esp_a2d_cb_param_t *a2d = NULL; - switch (event) { - case ESP_A2D_CONNECTION_STATE_EVT: { - LOG_DEBUG_EVENT(ESP_A2D_CONNECTION_STATE_EVT); - a2d = (esp_a2d_cb_param_t *)(param); - if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { - LOG_INFO("a2dp disconnected"); - s_a2d_state = APP_AV_STATE_UNCONNECTED; - esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); - } - break; - } - case ESP_A2D_AUDIO_STATE_EVT: - LOG_DEBUG_EVENT(ESP_A2D_AUDIO_STATE_EVT); - break; - case ESP_A2D_AUDIO_CFG_EVT: - LOG_DEBUG_EVENT(ESP_A2D_AUDIO_CFG_EVT); - break; - case ESP_A2D_MEDIA_CTRL_ACK_EVT: - LOG_DEBUG_EVENT(ESP_A2D_MEDIA_CTRL_ACK_EVT); - break; - case BT_APP_HEART_BEAT_EVT: - LOG_DEBUG_EVENT(BT_APP_HEART_BEAT_EVT); - break; - default: - LOG_ERROR("%s unhandled evt %d", __func__, event); - break; - } -} diff --git a/main/output_dac.c b/main/output_dac.c new file mode 100644 index 00000000..5b4ecb91 --- /dev/null +++ b/main/output_dac.c @@ -0,0 +1,342 @@ + +#include "squeezelite.h" +#include "driver/i2s.h" + +#include + +#define I2S_NUM (0) +#define I2S_BCK_IO (GPIO_NUM_26) +#define I2S_WS_IO (GPIO_NUM_25) +#define I2S_DO_IO (GPIO_NUM_22) +#define I2S_DI_IO (-1) + +#define TIMED_SECTION_START_MS_FORCE(x,force) { static time_t __aa_time_start = 0; if(hasTimeElapsed(&__aa_time_start,x,force)) { +#define TIMED_SECTION_START_MS(x) { static time_t __aa_time_start = 0; if(hasTimeElapsed(&__aa_time_start,x,false)){ +#define TIMED_SECTION_START_FORCE(x,force) TIMED_SECTION_START_MS(x * 1000UL,force) +#define TIMED_SECTION_START(x) TIMED_SECTION_START_MS(x * 1000UL) +#define TIMED_SECTION_END }} + +static log_level loglevel; + +static bool running = true; +static bool isI2SStarted=false; +extern struct outputstate output; +extern struct buffer *streambuf; +extern struct buffer *outputbuf; +static i2s_config_t i2s_config; +#if REPACK && BYTES_PER_FRAMES == 4 +#error "REPACK is not compatible with BYTES_PER_FRAME=4" +#endif + +#define LOCK mutex_lock(outputbuf->mutex) +#define UNLOCK mutex_unlock(outputbuf->mutex) + +#define FRAME_BLOCK MAX_SILENCE_FRAMES +#define DAC_OUTPUT_BUFFER_FRAMES FRAME_BLOCK +#define DAC_OUTPUT_BUFFER_RESERVE FRAME_BLOCK/2 +#define I2S_FRAME_SIZE 256 +#define FRAME_TO_BYTES(f) f*BYTES_PER_FRAME +#define BYTES_TO_FRAME(b) b/BYTES_PER_FRAME +#define FRAMES_TO_MS(f) 1000*f/output.current_sample_rate +#define BYTES_TO_MS(b) FRAMES_TO_MS(BYTES_TO_FRAME(b)) + +#define SET_MIN_MAX(val,var) var=val; if(varmax_##var) max_##var=var +#define RESET_MIN_MAX(var,mv) min_##var=mv##_MAX; max_##var=mv##_MIN +#define DECLARE_MIN_MAX(var,t,mv) static t min_##var = mv##_MAX, max_##var = mv##_MIN; t var=0 +#define DECLARE_ALL_MIN_MAX DECLARE_MIN_MAX(req, long,LONG); DECLARE_MIN_MAX(o, long,LONG); DECLARE_MIN_MAX(s, long,LONG); DECLARE_MIN_MAX(d, long,LONG); DECLARE_MIN_MAX(duration, long,LONG);DECLARE_MIN_MAX(buffering, long,LONG);DECLARE_MIN_MAX(totalprocess, long,LONG); +#define RESET_ALL_MIN_MAX RESET_MIN_MAX(d,LONG); RESET_MIN_MAX(o,LONG); RESET_MIN_MAX(s,LONG); RESET_MIN_MAX(req,LONG); RESET_MIN_MAX(duration,LONG);RESET_MIN_MAX(buffering,LONG);RESET_MIN_MAX(totalprocess,LONG); +extern u8_t *silencebuf; + +static u8_t *optr; +static int bytes_per_frame; +static thread_type thread; + +static int _dac_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, + s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr); +static void *output_thread(); +bool hasTimeElapsed(time_t * lastTime, time_t delayMS, bool bforce) +{ + if (*lastTime <= gettime_ms() ||bforce) + { + *lastTime = gettime_ms() + delayMS; + return true; + } + else + return false; +} +void set_volume(unsigned left, unsigned right) { + LOG_DEBUG("setting internal gain left: %u right: %u", left, right); + LOCK; + output.gainL = left; + output.gainR = right; + UNLOCK; +} + + +void output_init_dac(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { + loglevel = level; + optr = malloc(FRAME_TO_BYTES(DAC_OUTPUT_BUFFER_FRAMES)); + if (!optr) { + LOG_ERROR("unable to malloc buf"); + return; + } + LOG_INFO("init output DAC"); + + memset(&output, 0, sizeof(output)); + +#if BYTES_PER_FRAME == 4 + output.format = S16_LE; +#else + output.format = S32_LE; +#endif + output.start_frames = DAC_OUTPUT_BUFFER_FRAMES*2; + output.write_cb = &_dac_write_frames; + output.rate_delay = rate_delay; + + if (params) { + if (!strcmp(params, "32")) output.format = S32_LE; + if (!strcmp(params, "24")) output.format = S24_3LE; + if (!strcmp(params, "16")) output.format = S16_LE; + } + + // ensure output rate is specified to avoid test open + if (!rates[0]) { + rates[0] = 44100; + } + + output_init_common(level, device, output_buf_size, rates, idle); + + + i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX; // Only TX + i2s_config.sample_rate = output.current_sample_rate; + i2s_config.bits_per_sample = BYTES_PER_FRAME * 8/2; + i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; //2-channels + i2s_config.communication_format = I2S_COMM_FORMAT_I2S + | (output.format==S16_LE||output.format==S32_LE||output.format==S24_3LE)?I2S_COMM_FORMAT_I2S_LSB:I2S_COMM_FORMAT_I2S_MSB; + i2s_config.dma_buf_count = 6; //todo: tune this parameter. Expressed in numbrer of buffers. + i2s_config.dma_buf_len = I2S_FRAME_SIZE; // todo: tune this parameter. Expressed in number of samples. Byte size depends on bit depth + i2s_config.use_apll = false; + i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1 + + i2s_pin_config_t pin_config = { .bck_io_num = I2S_BCK_IO, .ws_io_num = + I2S_WS_IO, .data_out_num = I2S_DO_IO, .data_in_num = I2S_DI_IO //Not used + }; + LOG_INFO("Initializing I2S with rate: %d, bits per sample: %d, buffer len: %d, number of buffers: %d ", + i2s_config.sample_rate, i2s_config.bits_per_sample, i2s_config.dma_buf_len, i2s_config.dma_buf_count); + i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL); + i2s_set_pin(I2S_NUM, &pin_config); + i2s_set_clk(I2S_NUM, output.current_sample_rate, i2s_config.bits_per_sample, 2); + isI2SStarted=false; + i2s_stop(I2S_NUM); + +#if LINUX || OSX || FREEBSD || POSIX + pthread_attr_t attr; + pthread_attr_init(&attr); +#ifdef PTHREAD_STACK_MIN + pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); +#endif + pthread_create(&thread, &attr, output_thread, NULL); + pthread_attr_destroy(&attr); +#endif +#if WIN + thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread, NULL, 0, NULL); +#endif +} + +void output_close_dac(void) { + LOG_INFO("close output"); + + LOCK; + running = false; + UNLOCK; + free(optr); + output_close_common(); +} + +static int _dac_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, + s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr) { + + u8_t *obuf; + + if (!silence) { + + if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) { + _apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr); + } + +#if !REPACK + if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { + _apply_gain(outputbuf, out_frames, gainL, gainR); + } + + IF_DSD( + if (output.outfmt == DOP) { + update_dop((u32_t *) outputbuf->readp, out_frames, output.invert); + } else if (output.outfmt != PCM && output.invert) + dsd_invert((u32_t *) outputbuf->readp, out_frames); + ) + + memcpy(optr, outputbuf->readp, out_frames * BYTES_PER_FRAME); +#else + obuf = outputbuf->readp; +#endif + + } else { + + obuf = silencebuf; +#if !REPACK + IF_DSD( + if (output.outfmt != PCM) { + obuf = silencebuf_dsd; + update_dop((u32_t *) obuf, out_frames, false); // don't invert silence + } + ) + + memcpy(optr, obuf, out_frames * BYTES_PER_FRAME); +#endif + } + +#if REPACK + _scale_and_pack_frames(optr, (s32_t *)(void *)obuf, out_frames, gainL, gainR, output.format); +#endif +// TIMED_SECTION_START_MS(500); +// LOG_INFO("Done moving data to out buffer"); +// TIMED_SECTION_END; + return (int)out_frames; +} + +void wait_for_frames(size_t frames) +{ + usleep((1000* frames/output.current_sample_rate) ); +} + +static void *output_thread() { +// // buffer to hold output data so we can block on writing outside of output lock, allocated on init +// u8_t *obuf = malloc(FRAME_BLOCK * BYTES_PER_FRAME); + u8_t *opos=optr; + frames_t frames=0, requested_frames = 0; + size_t used_buffer=0; + static int count = 0, count2=0; + uint32_t start_writing=0, start_i2s=0; + DECLARE_ALL_MIN_MAX; + + size_t i2s_bytes_write, i2s_bytes_to_write = 0; +#if REPACK + LOCK; + + switch (output.format) { + case S32_BE: + case S32_LE: + bytes_per_frame = 4 * 2; break; + case S24_3LE: + case S24_3BE: + bytes_per_frame = 3 * 2; break; + case S16_LE: + case S16_BE: + bytes_per_frame = 2 * 2; break; + default: + bytes_per_frame = 4 * 2; break; + break; + } + + UNLOCK; +#else + bytes_per_frame = BYTES_PER_FRAME; +#endif + + + while (running) { + start_writing=esp_timer_get_time(); + LOCK; + + if (output.state == OUTPUT_OFF) { + UNLOCK; + LOG_INFO("Output state is off."); + isI2SStarted=false; + i2s_stop(I2S_NUM); + usleep(500000); + continue; + } + requested_frames = 0; + frames=0; + if(used_buffer==0) + { + // replenish buffer when it's empty + opos=optr; + requested_frames =DAC_OUTPUT_BUFFER_FRAMES; + + frames = _output_frames( requested_frames ); // Keep the dma buffer full + used_buffer+=FRAME_TO_BYTES(frames); + + } + UNLOCK; + if(frames>0) SET_MIN_MAX((esp_timer_get_time()-start_writing)/1000,buffering); + // todo: call i2s_set_clock here if rate is changed + + + if (used_buffer ) + { + start_i2s=esp_timer_get_time(); + if(!isI2SStarted) + { + isI2SStarted=true; + i2s_start(I2S_NUM); + } + i2s_write(I2S_NUM, opos,used_buffer, &i2s_bytes_write, portMAX_DELAY); + if(i2s_bytes_write!=used_buffer) + { + LOG_WARN("I2S DMA Overflow! available bytes: %d, I2S wrote %d bytes", used_buffer,i2s_bytes_write); + } + used_buffer -= i2s_bytes_write; + opos+=i2s_bytes_write; + output.device_frames =BYTES_TO_FRAME(used_buffer); + output.updated = gettime_ms(); + output.frames_played_dmp = output.frames_played-output.device_frames; + SET_MIN_MAX((esp_timer_get_time()-start_i2s)/1000,duration); + } + SET_MIN_MAX(duration+frames>0?buffering:0,totalprocess); + SET_MIN_MAX(_buf_used(outputbuf),o); + SET_MIN_MAX(_buf_used(streambuf),s); + SET_MIN_MAX(used_buffer,d); + SET_MIN_MAX(requested_frames,req); + if (!(count++ & 0x1ff)) { + LOG_INFO( "count:%d" + "\n ----------+----------+-----------+ +----------+----------+----------------+" + "\n max | min | current| | max | min | current |" + "\n (ms) | (ms) | (ms)| | (frames) | (frames) | (frames)|" + "\n ----------+----------+-----------+ +----------+----------+----------------+" + "\nout %10d|%10d|%11d|" " |%10d|%10d|%16d|" + "\nstream %10d|%10d|%11d|" " |%10d|%10d|%16d|" + "\nDMA overflow %10d|%10d|%11d|" " |%10d|%10d|%16d|" + "\nrequested %10d|%10d|%11d|" " |%10d|%10d|%16d|" + "\n ----------+----------+-----------+ +----------+----------+----------------+" + "\n" + "\n max (us) | min (us) | total(us) | " + "\n ----------+----------+-----------+ " + "\ni2s time (us):%10d|%10d|%11d|" + "\nbuffering(us):%10d|%10d|%11d|" + "\ntotal(us) :%10d|%10d|%11d|" + "\n ----------+----------+-----------+ ", + count, + BYTES_TO_MS(max_o), BYTES_TO_MS(min_o),BYTES_TO_MS(o),max_o,min_o,o, + BYTES_TO_MS(max_s), BYTES_TO_MS(min_s),BYTES_TO_MS(s),max_s,min_s,s, + BYTES_TO_MS(max_d),BYTES_TO_MS(min_d),BYTES_TO_MS(d),max_d,min_d,d, + FRAMES_TO_MS(max_req),FRAMES_TO_MS(min_req),FRAMES_TO_MS(req), max_req, min_req,req, + max_duration, min_duration, duration, + max_buffering, min_buffering, buffering, + max_totalprocess,min_totalprocess,totalprocess + ); + RESET_ALL_MIN_MAX; + } + } + + return 0; +} + +bool test_open(const char *device, unsigned rates[], bool userdef_rates) { + unsigned _rates[] = { 96000, 88200, 48000, 44100, 32000, 0 }; + memcpy(rates, _rates, sizeof(_rates)); + return true; +} + + diff --git a/main/output_dac.c.sample b/main/output_dac.c.sample deleted file mode 100644 index 7d755ff9..00000000 --- a/main/output_dac.c.sample +++ /dev/null @@ -1,211 +0,0 @@ - -#include "squeezelite.h" - -#include - -static log_level loglevel; - -static bool running = true; - -extern struct outputstate output; -extern struct buffer *outputbuf; - -#if REPACK && BYTES_PER_FRAMES == 4 -#error "REPACK is not compatible with BYTES_PER_FRAME=4" -#endif - -#define LOCK mutex_lock(outputbuf->mutex) -#define UNLOCK mutex_unlock(outputbuf->mutex) - -#define FRAME_BLOCK MAX_SILENCE_FRAMES - -extern u8_t *silencebuf; - -static u8_t *optr; -static int bytes_per_frame; -static thread_type thread; - -static int _dac_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, - s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr); -static void *output_thread(); - -void set_volume(unsigned left, unsigned right) { - LOG_DEBUG("setting internal gain left: %u right: %u", left, right); - LOCK; - output.gainL = left; - output.gainR = right; - UNLOCK; -} - -void output_init_dac(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { - loglevel = level; - - LOG_INFO("init output DAC"); - - memset(&output, 0, sizeof(output)); - -#if BYTES_PER_FRAME == 4 - output.format = S16_LE; -#else - output.format = S32_LE; -#endif - output.start_frames = FRAME_BLOCK * 2; - output.write_cb = &_dac_write_frames; - output.rate_delay = rate_delay; - - if (params) { - if (!strcmp(params, "32")) output.format = S32_LE; - if (!strcmp(params, "24")) output.format = S24_3LE; - if (!strcmp(params, "16")) output.format = S16_LE; - } - - // ensure output rate is specified to avoid test open - if (!rates[0]) { - rates[0] = 44100; - } - - output_init_common(level, device, output_buf_size, rates, idle); - -#if LINUX || OSX || FREEBSD || POSIX - pthread_attr_t attr; - pthread_attr_init(&attr); -#ifdef PTHREAD_STACK_MIN - pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); -#endif - pthread_create(&thread, &attr, output_thread, NULL); - pthread_attr_destroy(&attr); -#endif -#if WIN - thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread, NULL, 0, NULL); -#endif -} - -void output_close_dac(void) { - LOG_INFO("close output"); - - LOCK; - running = false; - UNLOCK; - - output_close_common(); -} - -static int _dac_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, - s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr) { - - u8_t *obuf; - - if (!silence) { - - if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) { - _apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr); - } - -#if !REPACK - if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { - _apply_gain(outputbuf, out_frames, gainL, gainR); - } - - IF_DSD( - if (output.outfmt == DOP) { - update_dop((u32_t *) outputbuf->readp, out_frames, output.invert); - } else if (output.outfmt != PCM && output.invert) - dsd_invert((u32_t *) outputbuf->readp, out_frames); - ) - - memcpy(optr, outputbuf->readp, out_frames * BYTES_PER_FRAME); -#else - obuf = outputbuf->readp; -#endif - - } else { - - obuf = silencebuf; -#if !REPACK - IF_DSD( - if (output.outfmt != PCM) { - obuf = silencebuf_dsd; - update_dop((u32_t *) obuf, out_frames, false); // don't invert silence - } - ) - - memcpy(optr, obuf, out_frames * BYTES_PER_FRAME); -#endif - } - -#if REPACK - _scale_and_pack_frames(optr, (s32_t *)(void *)obuf, out_frames, gainL, gainR, output.format); -#endif - - return (int)out_frames; -} - -static void *output_thread() { - // buffer to hold output data so we can block on writing outside of output lock, allocated on init - u8_t *obuf = malloc(FRAME_BLOCK * BYTES_PER_FRAME); - int frames = 0; - -#if REPACK - LOCK; - - switch (output.format) { - case S32_LE: - bytes_per_frame = 4 * 2; break; - case S24_3LE: - bytes_per_frame = 3 * 2; break; - case S16_LE: - bytes_per_frame = 2 * 2; break; - default: - bytes_per_frame = 4 * 2; break; - break; - } - - UNLOCK; -#else - bytes_per_frame = BYTES_PER_FRAME; -#endif - - while (running) { - - LOCK; - - if (output.state == OUTPUT_OFF) { - UNLOCK; - usleep(500000); - continue; - } - - output.device_frames = 0; - output.updated = gettime_ms(); - output.frames_played_dmp = output.frames_played; - - optr = obuf + frames * bytes_per_frame; - frames += _output_frames(FRAME_BLOCK); - - UNLOCK; - - if (frames) { - if (output.device[0] == '-' && memcmp(optr, silencebuf, frames * bytes_per_frame)) { - fwrite(obuf, bytes_per_frame, frames, stdout); - LOG_INFO("writing frames %d", frames); - } else { - // do something with some of these frames... - usleep((frames * 1000 * 1000) / output.current_sample_rate); - } - frames = 0; - } else { - usleep((FRAME_BLOCK * 1000 * 1000) / output.current_sample_rate); - } - - } - - return 0; -} - -bool test_open(const char *device, unsigned rates[], bool userdef_rates) { - unsigned _rates[] = { 96000, 88200, 48000, 44100, 32000, 0 }; - memcpy(rates, _rates, sizeof(_rates)); - return true; -} - - diff --git a/main/output_dac.c.tes b/main/output_dac.c.tes new file mode 100644 index 00000000..427d9267 --- /dev/null +++ b/main/output_dac.c.tes @@ -0,0 +1,163 @@ +#include "squeezelite.h" +#include "driver/i2s.h" + +static log_level loglevel; + +static bool running = true; + +extern struct outputstate output; +extern struct buffer *outputbuf; +extern struct buffer *streambuf; + +#define LOCK mutex_lock(outputbuf->mutex) +#define UNLOCK mutex_unlock(outputbuf->mutex) + +#define FRAME_BLOCK MAX_SILENCE_FRAMES + +extern u8_t *silencebuf; +extern u8_t *buf; + +#define I2S_NUM (0) +#define WAVE_FREQ_HZ (100) +#define PI (3.14159265) +#define I2S_BCK_IO (GPIO_NUM_26) +#define I2S_WS_IO (GPIO_NUM_25) +#define I2S_DO_IO (GPIO_NUM_22) +#define I2S_DI_IO (-1) +// buffer length is expressed in number of samples +#define I2S_BUF_LEN 60 + +static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, + s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, + ISAMPLE_T **cross_ptr); + +void set_volume(unsigned left, unsigned right) { + LOG_DEBUG("setting internal gain left: %u right: %u", left, right); + LOCK; + output.gainL = left; + output.gainR = right; + // TODO + output.gainL = FIXED_ONE; + output.gainR = FIXED_ONE; + UNLOCK; +} + +static void *output_thread(void *arg) { + bool start = true; + bool output_off = (output.state == OUTPUT_OFF); + bool probe_device = (arg != NULL); + int err; + + while (running) { + // todo: implement output off logic? + // todo: call i2s_set_clock here if rate is changed + LOCK; + + output.device_frames = 0; + output.updated = gettime_ms(); + output.frames_played_dmp = output.frames_played; + + _output_frames(I2S_BUF_LEN*2); // fill at least one DMA buffer with stereo signal + + UNLOCK; + } + + return 0; +} + +static pthread_t thread; + +void output_init_dac(log_level level, char *device, unsigned output_buf_size, + char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { + loglevel = level; + + LOG_INFO("init output DAC"); + + memset(&output, 0, sizeof(output)); + + output.start_frames = 0; //CONFIG_ //FRAME_BLOCK * 2; + output.write_cb = &_write_frames; + output.rate_delay = rate_delay; + + // ensure output rate is specified to avoid test open + if (!rates[0]) { + rates[0] = 44100; + } + device = "DAC"; + output_init_common(level, device, output_buf_size, rates, idle); + + i2s_config_t i2s_config = { + .mode = I2S_MODE_MASTER | I2S_MODE_TX, // Only TX + .sample_rate = output.current_sample_rate, + .bits_per_sample = BYTES_PER_FRAME * 8, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = I2S_COMM_FORMAT_I2S + | I2S_COMM_FORMAT_I2S_MSB, + .dma_buf_count = 6, //todo: tune this parameter. Expressed in numbrer of buffers + .dma_buf_len = I2S_BUF_LEN, // todo: tune this parameter. Expressed in number of samples. Byte size depends on bit depth + .use_apll = false, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1 //Interrupt level 1 + }; + i2s_pin_config_t pin_config = { .bck_io_num = I2S_BCK_IO, .ws_io_num = + I2S_WS_IO, .data_out_num = I2S_DO_IO, .data_in_num = I2S_DI_IO //Not used + }; + i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL); + i2s_set_pin(I2S_NUM, &pin_config); + i2s_set_clk(I2S_NUM, output.current_sample_rate, i2s_config.bits_per_sample, 2); + +#if LINUX || OSX || FREEBSD || POSIX + pthread_attr_t attr; + pthread_attr_init(&attr); +#ifdef PTHREAD_STACK_MIN + pthread_attr_setstacksize(&attr, + PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); +#endif + pthread_create(&thread, &attr, output_thread, NULL); + pthread_attr_destroy(&attr); +#endif +#if WIN + thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread, NULL, 0, NULL); +#endif + +} + +void output_close_dac(void) { + LOG_INFO("close output"); + LOCK; + running = false; + UNLOCK; + + output_close_common(); +} + +static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, + s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, + ISAMPLE_T **cross_ptr) { + + u8_t *obuf; + size_t i2s_bytes_write = 0; + + if (!silence) { + + if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) { + _apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr); + } + + obuf = outputbuf->readp; + + } else { + + obuf = silencebuf; + } + + //_scale_and_pack_frames(buf + buffill * bytes_per_frame, (s32_t *)(void *)obuf, out_frames, gainL, gainR, output.format); + +// buffill += out_frames; + i2s_write(I2S_NUM, obuf, out_frames *BYTES_PER_FRAME, &i2s_bytes_write, 100); + + + + return (int)i2s_bytes_write * BYTES_PER_FRAME; + +} + diff --git a/main/squeezelite.h b/main/squeezelite.h index 69c61c0e..0bc65b3b 100644 --- a/main/squeezelite.h +++ b/main/squeezelite.h @@ -81,6 +81,9 @@ #if defined(DACAUDIO) #undef DACAUDIO #define DACAUDIO 1 +#elif defined(BTAUDIO) +#undef BTAUDIO +#define BTAUDIO 1 #elif LINUX && !defined(PORTAUDIO) #define ALSA 1 #define PORTAUDIO 0 @@ -467,7 +470,24 @@ void logprint(const char *fmt, ...); #define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) #define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) #define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) +static inline void DEBUG_LOG_TIMED(uint32_t delayms, char * strFmt, ...) +{ + static log_level loglevel; + va_list args; + va_start(args, strFmt); + static uint32_t nextDebugLog=0; + if(esp_timer_get_time()>nextDebugLog) + { + if (loglevel >= lDEBUG) + { + logprint("%s %s:%d ", logtime(), __FUNCTION__, __LINE__); + logprint(strFmt , args); + logprint("\n"); + } + nextDebugLog=esp_timer_get_time()+delayms*1000; + } +} // utils.c (non logging) typedef enum { EVENT_TIMEOUT = 0, EVENT_READ, EVENT_WAKE } event_type; #if WIN && USE_SSL @@ -627,7 +647,7 @@ typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNIN 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; typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE, U8, U16_LE, U16_BE, U32_LE, U32_BE } output_format; #else -typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE } output_format; +typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE,S32_BE, S24_BE, S24_3BE, S16_BE } output_format; #endif typedef enum { FADE_INACTIVE = 0, FADE_DUE, FADE_ACTIVE } fade_state; @@ -726,8 +746,19 @@ void set_volume(unsigned left, unsigned right); bool test_open(const char *device, unsigned rates[], bool userdef_rates); void output_init_dac(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle); void output_close_dac(void); +void hal_bluetooth_init(log_level loglevel); #endif +//output_bt.c +#if BTAUDIO +void set_volume(unsigned left, unsigned right); +bool test_open(const char *device, unsigned rates[], bool userdef_rates); +void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle); +void output_close_bt(void); +void hal_bluetooth_init(log_level loglevel); +#endif + + // output_stdout.c void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay); void output_close_stdout(void);