mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-09 13:07:03 +03:00
tweak BT + start to add AirPlay
This commit is contained in:
@@ -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
components/airplay/airplay_sink.c
Normal file
65
components/airplay/airplay_sink.c
Normal file
@@ -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
components/airplay/airplay_sink.h
Normal file
22
components/airplay/airplay_sink.h
Normal file
@@ -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
components/airplay/component.mk
Normal file
10
components/airplay/component.mk
Normal file
@@ -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
|
||||
@@ -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,8 +452,16 @@ 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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user