ソースを参照

tweak BT + start to add AirPlay

philippe44 5 年 前
コミット
2a770483a1

+ 5 - 0
README.md

@@ -20,6 +20,11 @@ nvs_set autoexec2 str -v "squeezelite -o I2S -b 500:2000 -d all=info -m ESP32"
 
 nvs_set autoexec u8 -v 1		
 
+4/ set bluetooth & airplaysink name (if not set in menuconfig)
+
+nvs_set bt_sink_name str -v "<name>"
+nvs_set airplay_sink_name str -v "<name>"
+
 The "join" and "squeezelite" commands can also be typed at the prompt to start manually. Use "help" to see the list.
 
 The squeezelite options are very similar to the regular Linux ones. Differences are :

+ 65 - 0
components/airplay/airplay_sink.c

@@ -0,0 +1,65 @@
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "mdns.h"
+#include "nvs.h"
+#include "tcpip_adapter.h"
+#include "esp_log.h"
+#include "esp_console.h"
+#include "esp_pthread.h"
+#include "esp_system.h"
+#include "freertos/timers.h"
+#include "airplay_sink.h"
+
+#include "trace.h"
+
+static const char * TAG = "platform";
+extern char current_namespace[];
+
+void airplay_sink_init(void) {
+    const char *hostname;
+	char *airplay_name, sink_name[32] = CONFIG_AIRPLAY_NAME;
+	nvs_handle nvs;
+				
+	tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &hostname);
+
+    //initialize mDNS
+    ESP_ERROR_CHECK( mdns_init() );
+    ESP_ERROR_CHECK( mdns_hostname_set(hostname) );
+        
+    //structure with TXT records
+    mdns_txt_item_t serviceTxtData[] = {
+		{"am", "esp32"},
+		{"tp", "UDP"},
+		{"sm","false"}, 
+		{"sv","false"}, 
+		{"ek","1"},
+		{"et","0,1"},
+		{"md","0,1,2"},
+		{"cn","0,1"},
+		{"ch","2"},
+		{"ss","16"},
+		{"sr","44100"},
+		{"vn","3"},
+		{"txtvers","1"},
+	};
+	
+	if (nvs_open(current_namespace, NVS_READONLY, &nvs) == ESP_OK) {
+		size_t len = 31;
+		nvs_get_str(nvs, "airplay_sink_name", sink_name, &len);
+		nvs_close(nvs);
+	}	
+	
+	// AirPlay wants mDNS name to be MAC@name
+	uint8_t mac[6];	
+    esp_read_mac(mac, ESP_MAC_WIFI_STA);
+    asprintf(&airplay_name, "%02X%02X%02X%02X%02X%02X@%s",  mac[3], mac[4], mac[5], mac[3], mac[4], mac[5], sink_name);
+	
+	ESP_LOGI(TAG, "mdns hostname set to: [%s] with servicename %s", hostname, sink_name);
+
+    //initialize service
+    ESP_ERROR_CHECK( mdns_service_add(airplay_name, "_raop", "_tcp", 6000, serviceTxtData, sizeof(serviceTxtData) / sizeof(mdns_txt_item_t)) );
+	free(airplay_name);
+}

+ 22 - 0
components/airplay/airplay_sink.h

@@ -0,0 +1,22 @@
+/*
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#ifndef __AIRPLAY_SINK_H__
+#define __AIRPLAY_SINK_H__
+
+#include <stdint.h>
+
+//typedef enum { 	BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE, 
+				//BT_SINK_RATE, BT_SINK_VOLUME,  } bt_sink_cmd_t;
+
+/**
+ * @brief     init sink mode (need to be provided)
+ */
+void airplay_sink_init(void);
+
+#endif /* __AIRPLAY_SINK_H__*/

+ 10 - 0
components/airplay/component.mk

@@ -0,0 +1,10 @@
+#
+# Component Makefile
+#
+# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
+# this will take the sources in the src/ directory, compile them and link them into
+# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
+# please read the SDK documents if you need to do this.
+#
+
+CFLAGS += 	-I$(COMPONENT_PATH)/../tools

+ 15 - 4
components/driver_bt/bt_app_sink.c

@@ -21,6 +21,7 @@
 #include "esp_gap_bt_api.h"
 #include "esp_a2dp_api.h"
 #include "esp_avrc_api.h"
+#include "nvs.h"
 
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
@@ -39,9 +40,11 @@
 #define BT_RC_CT_TAG            "RCCT"
 
 #ifndef CONFIG_BT_SINK_NAME
-#define CONFIG_BT_SINK_NAME	"unavailable"
+#define CONFIG_BT_SINK_NAME	"default"
 #endif
 
+extern char current_namespace[];
+
 /* event for handler "bt_av_hdl_stack_up */
 enum {
     BT_APP_EVT_STACK_UP = 0,
@@ -449,9 +452,17 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
     switch (event) {
     case BT_APP_EVT_STACK_UP: {
         /* set up device name */
-        char *dev_name = CONFIG_BT_SINK_NAME;
-        esp_bt_dev_set_device_name(dev_name);
-
+		nvs_handle nvs;
+        char dev_name[32] = CONFIG_BT_SINK_NAME;
+				
+		if (nvs_open(current_namespace, NVS_READONLY, &nvs) == ESP_OK) {
+			size_t len = 31;
+			nvs_get_str(nvs, "bt_sink_name", dev_name, &len);
+			nvs_close(nvs);
+		}	
+				
+		esp_bt_dev_set_device_name(dev_name);
+		
         esp_bt_gap_register_callback(bt_app_gap_cb);
 
         /* initialize AVRCP controller */

+ 2 - 1
components/driver_bt/component.mk

@@ -7,5 +7,6 @@
 # please read the SDK documents if you need to do this.
 #
 
-CFLAGS += -I$(COMPONENT_PATH)/../tools
+CFLAGS += 	-I$(COMPONENT_PATH)/../tools
+			
 #CFLAGS += -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG

+ 2 - 1
components/squeezelite/component.mk

@@ -13,7 +13,8 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON
 	-I$(COMPONENT_PATH)/../tools				\
 	-I$(COMPONENT_PATH)/../codecs/inc/opus 		\
 	-I$(COMPONENT_PATH)/../codecs/inc/opusfile	\
-	-I$(COMPONENT_PATH)/../driver_bt
+	-I$(COMPONENT_PATH)/../driver_bt			\
+	-I$(COMPONENT_PATH)/../airplay
 
 #	-I$(COMPONENT_PATH)/../codecs/inc/faad2
 

+ 12 - 1
components/squeezelite/decode_bt.c → components/squeezelite/decode_external.c

@@ -21,6 +21,7 @@
  
 #include "squeezelite.h"
 #include "bt_app_sink.h"
+#include "airplay_sink.h"
 
 #define LOCK_O   mutex_lock(outputbuf->mutex)
 #define UNLOCK_O mutex_unlock(outputbuf->mutex)
@@ -92,15 +93,17 @@ static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...)
 		
 	switch(cmd) {
 	case BT_SINK_CONNECTED:
+		output.external = true;
 		output.state = OUTPUT_STOPPED;
 		LOG_INFO("BT sink started");
 		break;
 	case BT_SINK_DISCONNECTED:	
+		output.external = false;
 		output.state = OUTPUT_OFF;
 		LOG_INFO("BT sink stopped");
 		break;
 	case BT_SINK_PLAY:
-		output.state = OUTPUT_EXTERNAL;
+		output.state = OUTPUT_RUNNING;
 		LOG_INFO("BT sink playing");
 		break;
 	case BT_SINK_PAUSE:		
@@ -138,4 +141,12 @@ void register_other(void) {
 		LOG_WARN("Cannot be a BT sink and source");
 	}	
 #endif	
+#ifdef CONFIG_AIRPLAY_SINK
+	if (!strcasestr(output.device, "BT ")) {
+		airplay_sink_init();
+		LOG_INFO("Initializing AirPlay sink");		
+	} else {
+		LOG_WARN("Cannot be an AirPlay sink and BT source");
+	}	
+#endif
 }

+ 1 - 1
components/squeezelite/output_i2s.c

@@ -417,7 +417,7 @@ static void *output_thread_i2s() {
 			LOG_INFO("Output state is %d", output.state);
 			if (output.state == OUTPUT_OFF) led_blink(LED_GREEN, 100, 2500);
 			else if (output.state == OUTPUT_STOPPED) led_blink(LED_GREEN, 200, 1000);
-			else if (output.state >= OUTPUT_RUNNING) led_on(LED_GREEN);
+			else if (output.state == OUTPUT_RUNNING) led_on(LED_GREEN);
 		}
 		state = output.state;
 		

+ 4 - 3
components/squeezelite/slimproto.c

@@ -371,6 +371,7 @@ static void process_strm(u8_t *pkt, int len) {
 			sendSTAT("STMc", 0);
 			sentSTMu = sentSTMo = sentSTMl = false;
 			LOCK_O;
+			output.external = false;
 			output.threshold = strm->output_threshold;
 			output.next_replay_gain = unpackN(&strm->replay_gain);
 			output.fade_mode = strm->transition_type - '0';
@@ -688,7 +689,7 @@ static void slimproto_run() {
 			status.current_sample_rate = output.current_sample_rate;
 			status.updated = output.updated;
 			status.device_frames = output.device_frames;
-			
+						
 			if (output.track_started) {
 				_sendSTMs = true;
 				output.track_started = false;
@@ -703,7 +704,7 @@ static void slimproto_run() {
 			if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
 				output.state = OUTPUT_BUFFER;
 			}
-			if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
+			if (!output.external && output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
 				_decode_state == DECODE_STOPPED) {
 
 				_sendSTMu = true;
@@ -721,7 +722,7 @@ static void slimproto_run() {
 				output.state = OUTPUT_OFF;
 				LOG_DEBUG("output timeout");
 			}
-			if (output.state == OUTPUT_RUNNING && now - status.last > 1000) {
+			if (!output.external && output.state == OUTPUT_RUNNING && now - status.last > 1000) {
 				_sendSTMt = true;
 				status.last = now;
 			}

+ 2 - 1
components/squeezelite/squeezelite.h

@@ -634,7 +634,7 @@ bool resample_init(char *opt);
 
 // output.c output_alsa.c output_pa.c output_pack.c
 typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING, 
-			   OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT, OUTPUT_EXTERNAL } output_state;
+			   OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT } output_state;
 
 #if DSD
 typedef enum { PCM, DOP, DSD_U8, DSD_U16_LE, DSD_U32_LE, DSD_U16_BE, DSD_U32_BE, DOP_S24_LE, DOP_S24_3LE } dsd_format;
@@ -654,6 +654,7 @@ struct outputstate {
 	output_state state;
 	output_format format;
 	const char *device;
+	bool external;
 #if ALSA
 	unsigned buffer;
 	unsigned period;

+ 15 - 3
main/Kconfig.projbuild

@@ -229,7 +229,7 @@ menu "Squeezelite-ESP32"
 		config BT_SINK_NAME
 			depends on BT_SINK
 			string "Name of Bluetooth A2DP device"
-		        default "ESP32"
+		        default "ESP32-BT"
 		    help
 				This is the name of the bluetooth speaker that will be broadcasted			
 		config BT_SINK_PIN		
@@ -237,8 +237,20 @@ menu "Squeezelite-ESP32"
 			int "Bluetooth PIN code"
 		        default 1234
 		config AIRPLAY_SINK
-			bool "AirPlay receiver (not availabe now)"
-			default n
+			bool "AirPlay receiver"
+			default y
+		config AIRPLAY_NAME
+			depends on AIRPLAY_SINK
+			string "Name of AirPlay device"
+				default "ESP32-AirPlay"
+		    help
+				This is the name of the AirPlay speaker that will be broadcasted		
+		config AIRPLAY_PORT
+			depends on AIRPLAY_SINK
+			string "AirPlay listening port"
+				default 5000
+		    help
+				AirPlay service listening port
 	endmenu	
 
 endmenu