mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-11 22:17:17 +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
|
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 "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 :
|
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_gap_bt_api.h"
|
||||||
#include "esp_a2dp_api.h"
|
#include "esp_a2dp_api.h"
|
||||||
#include "esp_avrc_api.h"
|
#include "esp_avrc_api.h"
|
||||||
|
#include "nvs.h"
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
@@ -39,9 +40,11 @@
|
|||||||
#define BT_RC_CT_TAG "RCCT"
|
#define BT_RC_CT_TAG "RCCT"
|
||||||
|
|
||||||
#ifndef CONFIG_BT_SINK_NAME
|
#ifndef CONFIG_BT_SINK_NAME
|
||||||
#define CONFIG_BT_SINK_NAME "unavailable"
|
#define CONFIG_BT_SINK_NAME "default"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
extern char current_namespace[];
|
||||||
|
|
||||||
/* event for handler "bt_av_hdl_stack_up */
|
/* event for handler "bt_av_hdl_stack_up */
|
||||||
enum {
|
enum {
|
||||||
BT_APP_EVT_STACK_UP = 0,
|
BT_APP_EVT_STACK_UP = 0,
|
||||||
@@ -449,7 +452,15 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
|
|||||||
switch (event) {
|
switch (event) {
|
||||||
case BT_APP_EVT_STACK_UP: {
|
case BT_APP_EVT_STACK_UP: {
|
||||||
/* set up device name */
|
/* set up device name */
|
||||||
char *dev_name = CONFIG_BT_SINK_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_dev_set_device_name(dev_name);
|
||||||
|
|
||||||
esp_bt_gap_register_callback(bt_app_gap_cb);
|
esp_bt_gap_register_callback(bt_app_gap_cb);
|
||||||
|
|||||||
@@ -8,4 +8,5 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
CFLAGS += -I$(COMPONENT_PATH)/../tools
|
CFLAGS += -I$(COMPONENT_PATH)/../tools
|
||||||
|
|
||||||
#CFLAGS += -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG
|
#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)/../tools \
|
||||||
-I$(COMPONENT_PATH)/../codecs/inc/opus \
|
-I$(COMPONENT_PATH)/../codecs/inc/opus \
|
||||||
-I$(COMPONENT_PATH)/../codecs/inc/opusfile \
|
-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
|
# -I$(COMPONENT_PATH)/../codecs/inc/faad2
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "squeezelite.h"
|
#include "squeezelite.h"
|
||||||
#include "bt_app_sink.h"
|
#include "bt_app_sink.h"
|
||||||
|
#include "airplay_sink.h"
|
||||||
|
|
||||||
#define LOCK_O mutex_lock(outputbuf->mutex)
|
#define LOCK_O mutex_lock(outputbuf->mutex)
|
||||||
#define UNLOCK_O mutex_unlock(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) {
|
switch(cmd) {
|
||||||
case BT_SINK_CONNECTED:
|
case BT_SINK_CONNECTED:
|
||||||
|
output.external = true;
|
||||||
output.state = OUTPUT_STOPPED;
|
output.state = OUTPUT_STOPPED;
|
||||||
LOG_INFO("BT sink started");
|
LOG_INFO("BT sink started");
|
||||||
break;
|
break;
|
||||||
case BT_SINK_DISCONNECTED:
|
case BT_SINK_DISCONNECTED:
|
||||||
|
output.external = false;
|
||||||
output.state = OUTPUT_OFF;
|
output.state = OUTPUT_OFF;
|
||||||
LOG_INFO("BT sink stopped");
|
LOG_INFO("BT sink stopped");
|
||||||
break;
|
break;
|
||||||
case BT_SINK_PLAY:
|
case BT_SINK_PLAY:
|
||||||
output.state = OUTPUT_EXTERNAL;
|
output.state = OUTPUT_RUNNING;
|
||||||
LOG_INFO("BT sink playing");
|
LOG_INFO("BT sink playing");
|
||||||
break;
|
break;
|
||||||
case BT_SINK_PAUSE:
|
case BT_SINK_PAUSE:
|
||||||
@@ -138,4 +141,12 @@ void register_other(void) {
|
|||||||
LOG_WARN("Cannot be a BT sink and source");
|
LOG_WARN("Cannot be a BT sink and source");
|
||||||
}
|
}
|
||||||
#endif
|
#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);
|
LOG_INFO("Output state is %d", output.state);
|
||||||
if (output.state == OUTPUT_OFF) led_blink(LED_GREEN, 100, 2500);
|
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_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;
|
state = output.state;
|
||||||
|
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ static void process_strm(u8_t *pkt, int len) {
|
|||||||
sendSTAT("STMc", 0);
|
sendSTAT("STMc", 0);
|
||||||
sentSTMu = sentSTMo = sentSTMl = false;
|
sentSTMu = sentSTMo = sentSTMl = false;
|
||||||
LOCK_O;
|
LOCK_O;
|
||||||
|
output.external = false;
|
||||||
output.threshold = strm->output_threshold;
|
output.threshold = strm->output_threshold;
|
||||||
output.next_replay_gain = unpackN(&strm->replay_gain);
|
output.next_replay_gain = unpackN(&strm->replay_gain);
|
||||||
output.fade_mode = strm->transition_type - '0';
|
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)) {
|
if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
|
||||||
output.state = OUTPUT_BUFFER;
|
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) {
|
_decode_state == DECODE_STOPPED) {
|
||||||
|
|
||||||
_sendSTMu = true;
|
_sendSTMu = true;
|
||||||
@@ -721,7 +722,7 @@ static void slimproto_run() {
|
|||||||
output.state = OUTPUT_OFF;
|
output.state = OUTPUT_OFF;
|
||||||
LOG_DEBUG("output timeout");
|
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;
|
_sendSTMt = true;
|
||||||
status.last = now;
|
status.last = now;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -634,7 +634,7 @@ bool resample_init(char *opt);
|
|||||||
|
|
||||||
// output.c output_alsa.c output_pa.c output_pack.c
|
// output.c output_alsa.c output_pa.c output_pack.c
|
||||||
typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING,
|
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
|
#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;
|
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_state state;
|
||||||
output_format format;
|
output_format format;
|
||||||
const char *device;
|
const char *device;
|
||||||
|
bool external;
|
||||||
#if ALSA
|
#if ALSA
|
||||||
unsigned buffer;
|
unsigned buffer;
|
||||||
unsigned period;
|
unsigned period;
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ menu "Squeezelite-ESP32"
|
|||||||
config BT_SINK_NAME
|
config BT_SINK_NAME
|
||||||
depends on BT_SINK
|
depends on BT_SINK
|
||||||
string "Name of Bluetooth A2DP device"
|
string "Name of Bluetooth A2DP device"
|
||||||
default "ESP32"
|
default "ESP32-BT"
|
||||||
help
|
help
|
||||||
This is the name of the bluetooth speaker that will be broadcasted
|
This is the name of the bluetooth speaker that will be broadcasted
|
||||||
config BT_SINK_PIN
|
config BT_SINK_PIN
|
||||||
@@ -237,8 +237,20 @@ menu "Squeezelite-ESP32"
|
|||||||
int "Bluetooth PIN code"
|
int "Bluetooth PIN code"
|
||||||
default 1234
|
default 1234
|
||||||
config AIRPLAY_SINK
|
config AIRPLAY_SINK
|
||||||
bool "AirPlay receiver (not availabe now)"
|
bool "AirPlay receiver"
|
||||||
default n
|
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
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
Reference in New Issue
Block a user