Browse Source

Merge pull request #6 from philippe44/pr/4

Pr/4
sle118 5 years ago
parent
commit
c2889f051c
8 changed files with 1404 additions and 1035 deletions
  1. 829 0
      main/esp32.c
  2. 4 0
      main/esp_app_main.c
  3. 6 2
      main/main.c
  4. 27 820
      main/output_bt.c
  5. 342 0
      main/output_dac.c
  6. 0 211
      main/output_dac.c.sample
  7. 163 0
      main/output_dac.c.tes
  8. 33 2
      main/squeezelite.h

+ 829 - 0
main/esp32.c

@@ -3,8 +3,65 @@
 #include "sdkconfig.h"
 #include "esp_system.h" 
 #include "squeezelite.h"
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#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(var<min_##var) min_##var=var; if(var>max_##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;
+    }
+}

+ 4 - 0
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"
 

+ 6 - 2
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();

+ 27 - 820
main/output_bt.c

@@ -1,28 +1,6 @@
 #include "squeezelite.h"
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#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"
-};
-
-
-
-/* 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;
-}
+#endif
 
 
-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	
-
-	} else {
-
-		u8_t *buf = silencebuf;
-
-		memcpy(optr, buf, out_frames * 4);
 	}
-	
-	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;
-}
+		DEBUG_LOG_TIMED(200,"Silence flag true. Writing silence to audio out.");
 
-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;
+		u8_t *buf = silencebuf;
 
-                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;
-    }
-    }
-}
+		memcpy(bt_optr, buf, out_frames * 4);
+	}
 
-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;
-    }
-}
+	bt_optr += out_frames * 4;
 
-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;
-    }
+	return (int)out_frames;
 }

+ 342 - 0
main/output_dac.c

@@ -0,0 +1,342 @@
+
+#include "squeezelite.h"
+#include "driver/i2s.h"
+
+#include <signal.h>
+
+#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(var<min_##var) min_##var=var; if(var>max_##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;
+}
+
+

+ 0 - 211
main/output_dac.c.sample

@@ -1,211 +0,0 @@
-
-#include "squeezelite.h"
-
-#include <signal.h>
-
-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;
-}
-
-

+ 163 - 0
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;
+
+}
+

+ 33 - 2
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);