philippe44 5 éve
szülő
commit
ddfc03ea9b
11 módosított fájl, 1109 hozzáadás és 22 törlés
  1. 1 0
      .gitignore
  2. 17 12
      README.md
  3. 118 0
      bt_app_core.c
  4. 47 0
      bt_app_core.h
  5. 21 4
      esp_app_main.c
  6. 87 3
      main/Kconfig.projbuild
  7. 18 3
      main/esp32.c
  8. 4 0
      main/squeezelite.h
  9. 757 0
      output_bt.c
  10. 5 0
      partitions.csv
  11. 34 0
      sdkconfig.default

+ 1 - 0
.gitignore

@@ -56,3 +56,4 @@ $RECYCLE.BIN/
 
 # Windows shortcuts
 *.lnk
+sdkconfig

+ 17 - 12
README.md

@@ -1,24 +1,29 @@
-Adding squeezelite
- - libmad must be in a separated component otherwise linker whines about long call 
- - libfaad 
- 	- mlongcalls -O2 -DFIXED_POINT -DSMALL_STACK
+# Adding squeezelite
+- libmad must be in a separated component otherwise linker whines about long call 
+- libfaad
+	- mlongcalls -O2 -DFIXED_POINT -DSMALL_STACK
 	- change ac_link in configure and case ac_files, remove ''
 	- compiler but in cfft.c and cffti1, must disable optimization using 
-		#pragma GCC push_options
-		#pragma GCC optimize ("O0")
-		#pragma GCC pop_options
+			#pragma GCC push_options
+			#pragma GCC optimize ("O0")
+			#pragma GCC pop_options
  - libflac can use espressif's version	
  - vorbis
 	- set SPIRAM_MALLOC_ALWAYSINTERNAL to 2048 as it consumes a lot of 8K blocks and uses all internal memory - when no memoru, WiFI chip fails
  - set IDF_PATH=/home/esp-idf
  - set ESPPORT=COM9
- - change <esp-idf>\components\partition_table\partitions_singleapp.csv to 2M instead of 1M (or more)
- - change flash's size in serial flash config to 16M
- - change main stack size to 8000 as well (for app_main which is slimproto)
+ - <esp-idf>\components\partition_table\partitions_singleapp.csv to 2M instead of 1M (or more)
+ - sdkconfig.defaults now has configuration options set to load a local partitions.csv file that was setup with 2M size
+ - Make sure you validate the flash's size in serial flash config (for example set to 16M)
+ - sdkconfig.defaults has main stack size set to 8000
  - change SPIRAM_MALLOC_ALWAYSINTERNAL to 2048 so that vorbis does not exhaust ISRAM, but allocates to SPIRAM instead. When it is echausted, WiFi driver can't allocate SPIRAM (although it should and setting the option to ask it to allocated SPIRAM does not work)
- - use old "make" environment no CMake
- 
+ - Other options are available through menuconfig. Ideally, build should be reconfigured or at least compared with sdkconfig.default
  
+# Supporting Bluetooth a2dp output
+- menuconfig now has a section for setting output type
+- Output types are A2DP or DAC over I2S
+- When A2DP is chosen, the audio device name has to be specified here
+
 # Wifi SCAN Example
 
 This example shows how to use scan of ESP32.

+ 118 - 0
bt_app_core.c

@@ -0,0 +1,118 @@
+/*
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include "squeezelite.h"
+#include <stdint.h>
+
+#include <string.h>
+#include <stdbool.h>
+#include "freertos/xtensa_api.h"
+#include "freertos/FreeRTOSConfig.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/queue.h"
+#include "freertos/task.h"
+#include "esp_log.h"
+#include "bt_app_core.h"
+
+static void bt_app_task_handler(void *arg);
+static bool bt_app_send_msg(bt_app_msg_t *msg);
+static void bt_app_work_dispatched(bt_app_msg_t *msg);
+
+static log_level loglevel;
+
+static xQueueHandle s_bt_app_task_queue = NULL;
+static xTaskHandle s_bt_app_task_handle = NULL;
+void bt_set_log_level(log_level level){
+	loglevel = level;
+}
+bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback)
+{
+	LOG_DEBUG("%s event 0x%x, param len %d", __func__, event, param_len);
+
+    bt_app_msg_t msg;
+    memset(&msg, 0, sizeof(bt_app_msg_t));
+
+    msg.sig = BT_APP_SIG_WORK_DISPATCH;
+    msg.event = event;
+    msg.cb = p_cback;
+
+    if (param_len == 0) {
+        return bt_app_send_msg(&msg);
+    } else if (p_params && param_len > 0) {
+        if ((msg.param = malloc(param_len)) != NULL) {
+            memcpy(msg.param, p_params, param_len);
+            /* check if caller has provided a copy callback to do the deep copy */
+            if (p_copy_cback) {
+                p_copy_cback(&msg, msg.param, p_params);
+            }
+            return bt_app_send_msg(&msg);
+        }
+    }
+
+    return false;
+}
+
+static bool bt_app_send_msg(bt_app_msg_t *msg)
+{
+    if (msg == NULL) {
+        return false;
+    }
+
+    if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) {
+    	LOG_ERROR("%s xQueue send failed", __func__);
+        return false;
+    }
+    return true;
+}
+
+static void bt_app_work_dispatched(bt_app_msg_t *msg)
+{
+    if (msg->cb) {
+        msg->cb(msg->event, msg->param);
+    }
+}
+
+static void bt_app_task_handler(void *arg)
+{
+    bt_app_msg_t msg;
+    for (;;) {
+        if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) {
+            LOG_DEBUG("%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
+            switch (msg.sig) {
+            case BT_APP_SIG_WORK_DISPATCH:
+                bt_app_work_dispatched(&msg);
+                break;
+            default:
+                LOG_WARN("%s, unhandled sig: %d", __func__, msg.sig);
+                break;
+            } // switch (msg.sig)
+
+            if (msg.param) {
+                free(msg.param);
+            }
+        }
+    }
+}
+
+void bt_app_task_start_up(void)
+{
+    s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
+    xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle);
+    return;
+}
+
+void bt_app_task_shut_down(void)
+{
+    if (s_bt_app_task_handle) {
+        vTaskDelete(s_bt_app_task_handle);
+        s_bt_app_task_handle = NULL;
+    }
+    if (s_bt_app_task_queue) {
+        vQueueDelete(s_bt_app_task_queue);
+        s_bt_app_task_queue = NULL;
+    }
+}

+ 47 - 0
bt_app_core.h

@@ -0,0 +1,47 @@
+/*
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#ifndef __BT_APP_CORE_H__
+#define __BT_APP_CORE_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#define BT_APP_CORE_TAG                   "BT_APP_CORE"
+
+#define BT_APP_SIG_WORK_DISPATCH          (0x01)
+
+/**
+ * @brief     handler for the dispatched work
+ */
+typedef void (* bt_app_cb_t) (uint16_t event, void *param);
+
+/* message to be sent */
+typedef struct {
+    uint16_t             sig;      /*!< signal to bt_app_task */
+    uint16_t             event;    /*!< message event id */
+    bt_app_cb_t          cb;       /*!< context switch callback */
+    void                 *param;   /*!< parameter area needs to be last */
+} bt_app_msg_t;
+
+/**
+ * @brief     parameter deep-copy function to be customized
+ */
+typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
+
+/**
+ * @brief     work dispatcher for the application task
+ */
+bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback);
+
+void bt_app_task_start_up(void);
+
+void bt_app_task_shut_down(void);
+void bt_set_log_level(log_level level);
+#endif /* __BT_APP_CORE_H__ */

+ 21 - 4
main/scan.c → esp_app_main.c

@@ -21,6 +21,7 @@
     can be sorted based on Authentication Mode or Signal Strength. The priority
     for the Authentication mode is:  WPA2 > WPA > WEP > Open
 */
+#include "squeezelite.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/event_groups.h"
 #include "esp_wifi.h"
@@ -113,6 +114,7 @@ static void wifi_scan(void)
 
 int main(int argc, char**argv);
 
+
 void app_main()
 {
 	int i; 
@@ -123,17 +125,32 @@ void app_main()
 		"-n",
 		"ESP32",
 		"-d",
-		"all=info",
+		"slimproto=" CONFIG_LOGGING_SLIMPROTO,
+		"-d",
+		"stream=" CONFIG_LOGGING_STREAM,
+		"-d",
+		"decode=" CONFIG_LOGGING_DECODE,
+		"-d",
+		"output=" CONFIG_LOGGING_OUTPUT,
 		"-b",
-		"256:2000",
+		"256:2000"
+
 	};
-	
+
+
 	// can't do strtok on FLASH strings
 	argv = malloc(sizeof(_argv));
 	for (i = 0; i < sizeof(_argv)/sizeof(char*); i++) {
 		argv[i] = strdup(_argv[i]);
 	}
-	
+
+	logprint("%s %s:%d Calling main with parameters: " , logtime(), __FUNCTION__, __LINE__);
+
+	for (i = 0; i < sizeof(_argv)/sizeof(char*); i++) {
+		logprint("%s " , _argv[i]);
+	}
+	logprint("\n");
+
     // Initialize NVS
     esp_err_t ret = nvs_flash_init();
     if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {

+ 87 - 3
main/Kconfig.projbuild

@@ -1,5 +1,33 @@
-menu "Example Configuration"
-
+menu "Squeezelite-ESP32"
+	menu "Logging"
+		config LOGGING_SLIMPROTO
+        	string "logging level for slimproto "
+        	default "info"
+        	help
+        		Set logging level info|debug|sdebug
+		config LOGGING_STREAM
+        	string "logging level for stream "
+        	default "info"
+        	help
+        		Set logging level info|debug|sdebug
+   		config LOGGING_DECODE
+        	string "logging level for decode"
+        	default "info"
+        	help
+        		Set logging level info|debug|sdebug 	
+   		config LOGGING_OUTPUT
+        	string "logging level for output"
+        	default "info"
+        	help
+        		Set logging level info|debug|sdebug 	
+        		
+	endmenu
+	config LOG_OPTION
+		string "squeezelite log option"
+		default "all=info"
+		help 
+			log=level	Set logging level, logs: all|slimproto|stream|decode|output, level: info|debug|sdebug
+	menu "Wifi Configuration"
     config WIFI_SSID
         string "WiFi SSID"
         default "myssid"
@@ -11,7 +39,7 @@ menu "Example Configuration"
         default "mypassword"
         help
             WiFi password (WPA or WPA2) for the example to use.
-
+  
     choice SCAN_METHOD
         prompt "scan method"
         default WIFI_FAST_SCAN
@@ -64,5 +92,61 @@ menu "Example Configuration"
         config EXAMPLE_WPA2
             bool "wpa2"
     endchoice
+    endmenu
+    menu "Audio CODEC libraries"
+    config INCLUDE_FLAC
+        bool "FLAC"
+        default 1
+        help
+            Include FLAC library for flc decoding.
+    config INCLUDE_FAAD
+        bool "FAAD"
+        default 1
+        help
+            Include FAAD library for aac decoding.
+    config INCLUDE_MAD
+    	depends on SPIRAM_SUPPORT
+        bool "MAD"
+        default 1
+        help
+            Include mad library for mp3 decoding.
+	config INCLUDE_VORBIS
+        bool "VORBIS"
+        default 1
+        help
+            Include vorbis/ogg library for ogg/vorbis decoding.			
+	config INCLUDE_ALAC
+        bool "ALAC"
+        default 1
+        help
+            Include alac library for alac decoding.			
+	   
+    endmenu
+	menu "Audio Output"
+		choice OUTPUT_TYPE
+	        prompt "Output Type"
+	        default DACAUDIO
+	        help
+	            Type of output for squeezelite to send audio to
+	        config DACAUDIO
+	            bool "DAC over I2S"
+	        config BTAUDIO
+	            bool "Bluetooth A2DP"
+	    endchoice
+	    
+	    config A2DP_SINK_NAME
+	        string "Name of Bluetooth A2DP device"
+	        depends on BTAUDIO
+	        default "SMSL BT4.2"
+	        help
+	            This is the name of the bluetooth speaker that Squeezelite will try connecting to.
+	   
+	    config A2DP_DEV_NAME
+	        string "Name of Squeezelite device to use when connecting to A2DP device"
+	        depends on BTAUDIO
+	        default "Squeezelite"
+	        help
+	            This is the name of the device that the Bluetooth speaker will see when it is connected to.
+	endmenu
 
 endmenu

+ 18 - 3
main/esp32.c

@@ -1,5 +1,6 @@
 #include <signal.h>
 
+#include "sdkconfig.h"
 #include "esp_system.h" 
 #include "squeezelite.h"
 
@@ -22,24 +23,38 @@ struct codec *register_mpg(void) {
 	return NULL;
 }
 
-#ifndef CONFIG_AUDIO_FAAD
+#if !CONFIG_INCLUDE_FAAD
 struct codec *register_faad(void) {
 	LOG_INFO("aac unavailable");
 	return NULL;
 }
 #endif
 
-#ifndef CONFIG_AUDIO_MAD
+#if !CONFIG_INCLUDE_MAD
 struct codec *register_mad(void) {
 	LOG_INFO("mad unavailable");
 	return NULL;
 }
 #endif
 
-#ifndef CONFIG_AUDIO_FLAC
+#if !CONFIG_INCLUDE_FLAC
 struct codec *register_flac(void) {
 	LOG_INFO("flac unavailable");
 	return NULL;
 }
 #endif
 
+#if !CONFIG_INCLUDE_VORBIS
+struct codec *register_vorbis(void) {
+	LOG_INFO("vorbis unavailable");
+	return NULL;
+}
+#endif
+
+#if !CONFIG_INCLUDE_ALAC
+struct codec *register_alac(void) {
+	LOG_INFO("alac unavailable");
+	return NULL;
+}
+#endif
+

+ 4 - 0
main/squeezelite.h

@@ -270,7 +270,11 @@
 #include <arpa/inet.h>
 #include <sys/time.h>
 #include <sys/socket.h>
+#if POSIX
+#include <sys/poll.h>
+#else
 #include <poll.h>
+#endif
 #if !LINKALL
 #include <dlfcn.h>
 #endif

+ 757 - 0
output_bt.c

@@ -0,0 +1,757 @@
+#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;
+
+extern struct outputstate output;
+extern struct buffer *outputbuf;
+
+#define LOCK   mutex_lock(outputbuf->mutex)
+#define UNLOCK mutex_unlock(outputbuf->mutex)
+
+#define FRAME_BLOCK MAX_SILENCE_FRAMES
+
+extern u8_t *silencebuf;
+
+// buffer to hold output data so we can block on writing outside of output lock, allocated on init
+static u8_t *buf;
+static unsigned buffill;
+static int bytes_per_frame;
+
+static int _bt_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
+								s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr);
+
+void set_volume(unsigned left, unsigned right) {}
+
+
+/* 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,
+};
+
+/* 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,
+};
+
+#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 int s_connecting_intv = 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, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
+	loglevel = level;
+
+	LOG_INFO("init output BT");
+
+	buf = malloc(FRAME_BLOCK * BYTES_PER_FRAME);
+	if (!buf) {
+		LOG_ERROR("unable to malloc buf");
+		return;
+	}
+	buffill = 0;
+
+	memset(&output, 0, sizeof(output));
+
+	output.format = S32_LE;
+	output.start_frames = FRAME_BLOCK * 2;
+	output.write_cb = &_bt_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;
+	}
+	/*
+	 * 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);
+
+/*
+ * Bluetooth audio source init Start
+ */
+
+
+	output_init_common(level, "-", 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;
+
+	free(buf);
+
+
+
+	output_close_common();
+}
+static u8_t *optr;
+static int _bt_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
+								s32_t cross_gain_in, s32_t cross_gain_out, s32_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);
+		}
+
+		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;
+
+	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;
+}
+
+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;
+    esp_bt_gap_dev_prop_t *p;
+
+    LOG_INFO("Scanned 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("--Class of Device: 0x%x", cod);
+            break;
+        case ESP_BT_GAP_DEV_PROP_RSSI:
+            rssi = *(int8_t *)(p->val);
+            LOG_INFO("--RSSI: %d", rssi);
+            break;
+        case ESP_BT_GAP_DEV_PROP_EIR:
+            eir = (uint8_t *)(p->val);
+            break;
+        case ESP_BT_GAP_DEV_PROP_BDNAME:
+        default:
+            break;
+        }
+    }
+
+    /* search for device with MAJOR service class as "rendering" in COD */
+    if (!esp_bt_gap_is_valid_cod(cod) ||
+            !(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) {
+        return;
+    }
+
+    /* search for device named "ESP_SPEAKER" in its extended inqury response */
+    if (eir) {
+        get_name_from_eir(eir, s_peer_bdname, NULL);
+        if (strcmp((char *)s_peer_bdname, CONFIG_A2DP_SINK_NAME) != 0) {
+            return;
+        }
+
+        LOG_INFO("Found a target device, address %s, name %s", bda_str, s_peer_bdname);
+        s_a2d_state = APP_AV_STATE_DISCOVERED;
+        memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
+        LOG_INFO("Cancel device discovery ...");
+        esp_bt_gap_cancel_discovery();
+    }
+}
+
+
+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) {
+                s_a2d_state = APP_AV_STATE_CONNECTING;
+                LOG_INFO("Device discovery stopped.");
+                LOG_INFO("a2dp connecting to peer: %s", s_peer_bdname);
+                esp_a2d_source_connect(s_peer_bda);
+            } 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.");
+        }
+        break;
+    }
+    case ESP_BT_GAP_RMT_SRVCS_EVT:
+    case 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;
+    case ESP_BT_GAP_KEY_REQ_EVT:
+        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)
+{
+	LOG_DEBUG("%s evt %d", __func__, event);
+    switch (event) {
+    case BT_APP_EVT_STACK_UP: {
+        /* set up device name */
+        char *dev_name = CONFIG_A2DP_DEV_NAME;
+        esp_bt_dev_set_device_name(dev_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", (10000 / 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)
+{
+
+     int32_t ret = 0;
+	frames_t frames=0;
+	frames_t frames_wanted = 0;
+
+    if (len < 0 || data == NULL) {
+        return 0;
+    }
+    optr = (u8_t *)data;
+
+   	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;
+
+   	frames_wanted = len * bytes_per_frame;
+   	ret = len;
+
+    	LOCK;
+
+
+    	output.device_frames = 0;
+    	output.updated = gettime_ms();
+    	output.frames_played_dmp = output.frames_played;
+
+    	do {
+    		frames = _output_frames(frames_wanted);
+    		frames_wanted -= frames;
+    	} while (frames_wanted > 0 && frames != 0);
+
+    	if (frames_wanted > 0) {
+    		LOG_DEBUG("pad with silence");
+    		memset(optr, 0, frames_wanted * bytes_per_frame);
+    	}
+
+    	if (output.state == OUTPUT_OFF) {
+    		LOG_INFO("output off");
+    		ret = 0;
+    	}
+
+    	UNLOCK;
+
+    	return ret;
+}
+
+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_INFO("%s state %d, evt 0x%x", __func__, s_a2d_state, event);
+    switch (s_a2d_state) {
+    case APP_AV_STATE_DISCOVERING:
+    case APP_AV_STATE_DISCOVERED:
+        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:
+    case ESP_A2D_AUDIO_STATE_EVT:
+    case ESP_A2D_AUDIO_CFG_EVT:
+    case ESP_A2D_MEDIA_CTRL_ACK_EVT:
+        break;
+    case BT_APP_HEART_BEAT_EVT: {
+        uint8_t *p = s_peer_bda;
+        LOG_INFO("a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x",
+                 p[0], p[1], p[2], p[3], p[4], p[5]);
+        esp_a2d_source_connect(s_peer_bda);
+        s_a2d_state = APP_AV_STATE_CONNECTING;
+        s_connecting_intv = 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");
+            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:
+    case ESP_A2D_AUDIO_CFG_EVT:
+    case ESP_A2D_MEDIA_CTRL_ACK_EVT:
+        break;
+    case BT_APP_HEART_BEAT_EVT:
+        if (++s_connecting_intv >= 2) {
+            s_a2d_state = APP_AV_STATE_UNCONNECTED;
+            s_connecting_intv = 0;
+        }
+        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) {
+            LOG_INFO("a2dp media ready checking ...");
+            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 ...");
+                esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START);
+                s_media_state = APP_AV_MEDIA_STATE_STARTING;
+            }
+        }
+        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 start 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 (++s_intv_cnt >= 10) {
+                LOG_INFO("a2dp media stopping...");
+                esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
+                s_media_state = APP_AV_MEDIA_STATE_STOPPING;
+                s_intv_cnt = 0;
+            }
+        }
+        break;
+    }
+    case 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, disconnecting...");
+                s_media_state = APP_AV_MEDIA_STATE_IDLE;
+                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: {
+        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
+        break;
+    case ESP_A2D_MEDIA_CTRL_ACK_EVT:
+    case 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: {
+        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:
+    case ESP_A2D_AUDIO_CFG_EVT:
+    case ESP_A2D_MEDIA_CTRL_ACK_EVT:
+    case BT_APP_HEART_BEAT_EVT:
+        break;
+    default:
+        LOG_ERROR("%s unhandled evt %d", __func__, event);
+        break;
+    }
+}

+ 5 - 0
partitions.csv

@@ -0,0 +1,5 @@
+# Name,   Type, SubType, Offset,  Size, Flags
+# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
+nvs,      data, nvs,     0x9000,  0x6000,
+phy_init, data, phy,     0xf000,  0x1000,
+factory,  app,  factory, 0x10000, 2000000,

+ 34 - 0
sdkconfig.default

@@ -0,0 +1,34 @@
+# Override some defaults so BT stack is enabled and
+# Classic BT is enabled
+CONFIG_BT_ENABLED=y
+CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=
+CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY=y
+CONFIG_BTDM_CONTROLLER_MODE_BTDM=
+CONFIG_BLUEDROID_ENABLED=y
+CONFIG_CLASSIC_BT_ENABLED=y
+CONFIG_A2DP_ENABLE=y
+CONFIG_BT_SPP_ENABLED=n
+CONFIG_GATTS_ENABLE=n
+CONFIG_GATTC_ENABLE=n
+CONFIG_BLE_SMP_ENABLE=n
+#enable SPIRAM
+CONFIG_SPIRAM_SUPPORT=y
+CONFIG_SPIRAM_BOOT_INIT=y
+CONFIG_SPIRAM_USE_MALLOC=y
+CONFIG_SPIRAM_TYPE_AUTO=y
+CONFIG_SPIRAM_SIZE=-1
+CONFIG_SPIRAM_SPEED_40M=y
+CONFIG_SPIRAM_MEMTEST=y
+CONFIG_SPIRAM_CACHE_WORKAROUND=y
+CONFIG_SPIRAM_BANKSWITCH_ENABLE=y
+CONFIG_SPIRAM_BANKSWITCH_RESERVE=8
+CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
+CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
+CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=2048
+CONFIG_PICO_PSRAM_CS_IO=10
+CONFIG_MAIN_TASK_STACK_SIZE=8000
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
+CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
+CONFIG_PARTITION_TABLE_OFFSET=0x8000
+CONFIG_PARTITION_TABLE_MD5=y