Finalize buttons for AirPlay and BT, worked on coexistence

This commit is contained in:
philippe44
2020-01-04 02:03:34 -08:00
parent d0d98c778b
commit 540466e746
18 changed files with 361 additions and 152 deletions

View File

@@ -86,7 +86,7 @@ void down(void *id, button_event_e event, button_press_e press, bool longpress)
/****************************************************************************************
*
*/
void actrls_init(int n, actrls_config_t *config) {
void actrls_init(int n, const actrls_config_t *config) {
for (int i = 0; i < n; i++) {
button_create(config + i, config[i].gpio, config[i].type, config[i].pull, control_handler, config[i].long_press, config[i].shifter_gpio);
}
@@ -95,7 +95,7 @@ void actrls_init(int n, actrls_config_t *config) {
/****************************************************************************************
*
*/
void actrls_set_default(actrls_t controls) {
void actrls_set_default(const actrls_t controls) {
memcpy(default_controls, controls, sizeof(actrls_t));
memcpy(current_controls, default_controls, sizeof(actrls_t));
}
@@ -103,7 +103,7 @@ void actrls_set_default(actrls_t controls) {
/****************************************************************************************
*
*/
void actrls_set(actrls_t controls) {
void actrls_set(const actrls_t controls) {
memcpy(current_controls, controls, sizeof(actrls_t));
}

View File

@@ -36,7 +36,7 @@ typedef struct {
actrls_action_e normal[2], longpress[2], shifted[2], longshifted[2]; // [0] keypressed, [1] keyreleased
} actrls_config_t;
void actrls_init(int n, actrls_config_t *config);
void actrls_set_default(actrls_t controls);
void actrls_set(actrls_t controls);
void actrls_init(int n, const actrls_config_t *config);
void actrls_set_default(const actrls_t controls);
void actrls_set(const actrls_t controls);
void actrls_unset(void);

View File

@@ -36,10 +36,11 @@ static const char * TAG = "audio_controls";
static int n_buttons = 0;
#define BUTTON_STACK_SIZE 4096
#define MAX_BUTTONS 16
#define DEBOUNCE 50
static struct button_s {
static EXT_RAM_ATTR struct button_s {
void *id;
int gpio, index;
button_handler handler;
@@ -149,6 +150,8 @@ void dummy_handler(void *id, button_event_e event, button_press_e press) {
* Create buttons
*/
void button_create(void *id, int gpio, int type, bool pull, button_handler handler, int long_press, int shifter_gpio) {
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
static EXT_RAM_ATTR StackType_t xStack[BUTTON_STACK_SIZE] __attribute__ ((aligned (4)));
if (n_buttons >= MAX_BUTTONS) return;
@@ -157,7 +160,7 @@ void button_create(void *id, int gpio, int type, bool pull, button_handler handl
if (!n_buttons) {
button_evt_queue = xQueueCreate(10, sizeof(struct button_s));
gpio_install_isr_service(0);
xTaskCreate(buttons_task, "buttons_task", 8192, NULL, ESP_TASK_PRIO_MIN + 1, NULL);
xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons_thread", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
}
// just in case this structure is allocated in a future release

View File

@@ -49,7 +49,7 @@ enum {
};
char * bt_name = NULL;
static void (*bt_app_a2d_cmd_cb)(bt_sink_cmd_t cmd, ...);
static bool (*bt_app_a2d_cmd_cb)(bt_sink_cmd_t cmd, ...);
static void (*bt_app_a2d_data_cb)(const uint8_t *data, uint32_t len);
/* handler for bluetooth stack enabled events */
@@ -84,42 +84,35 @@ static void bt_volume_down(void) {
// volume UP/DOWN buttons are not supported by iPhone/Android
volume_set_by_local_host(s_volume > 3 ? s_volume - 3 : 0);
(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
ESP_LOGD(BT_AV_TAG, "BT volume down %u", s_volume);
}
static void bt_toggle(void) {
if (s_play_status != ESP_AVRC_PLAYBACK_PLAYING) esp_avrc_ct_send_passthrough_cmd(tl++, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
else esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
//s_audio_state = ESP_A2D_AUDIO_STATE_STOPPED;
ESP_LOGD(BT_AV_TAG, "BT play/pause toggle %u", s_volume);
}
static void bt_play(void) {
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
ESP_LOGD(BT_AV_TAG, "BT play");
}
static void bt_pause(void) {
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_PAUSE, ESP_AVRC_PT_CMD_STATE_PRESSED);
ESP_LOGD(BT_AV_TAG, "BT pause");
}
static void bt_stop(void) {
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
ESP_LOGD(BT_AV_TAG, "BT stop");
}
static void bt_prev(void) {
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_BACKWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
ESP_LOGD(BT_AV_TAG, "BT previous");
}
static void bt_next(void) {
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_FORWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
ESP_LOGD(BT_AV_TAG, "BT next");
}
static actrls_t controls = {
const static actrls_t controls = {
bt_volume_up, bt_volume_down, // volume up, volume down
bt_toggle, bt_play, // toggle, play
bt_pause, bt_stop, // pause, stop
@@ -127,16 +120,18 @@ static actrls_t controls = {
bt_prev, bt_next, // prev, next
};
void bt_sink_cmd(bt_sink_cmd_t event, ...) {
switch(event) {
case BT_SINK_DISCONNECTED:
/* taking/giving audio system's control */
void bt_master(bool on) {
if (on) actrls_set(controls);
else actrls_unset();
}
/* disconnection */
void bt_disconnect(void) {
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
esp_a2d_sink_disconnect(s_remote_bda);
actrls_unset();
break;
default:
ESP_LOGW(BT_AV_TAG, "unhandled command %u", event);
break;
}
ESP_LOGI(BT_AV_TAG, "forced disconnection");
}
/* callback for A2DP sink */
@@ -218,8 +213,10 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){
memcpy(s_remote_bda, bda, 6);
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
(*bt_app_a2d_cmd_cb)(BT_SINK_CONNECTED);
actrls_set(controls);
if (!(*bt_app_a2d_cmd_cb)(BT_SINK_CONNECTED)){
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
esp_a2d_sink_disconnect(s_remote_bda);
}
}
break;
}

View File

@@ -14,7 +14,7 @@
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;
typedef void (*bt_cmd_cb_t)(bt_sink_cmd_t cmd, ...);
typedef bool (*bt_cmd_cb_t)(bt_sink_cmd_t cmd, ...);
typedef void (*bt_data_cb_t)(const uint8_t *data, uint32_t len);
/**
@@ -28,8 +28,13 @@ void bt_sink_init(bt_cmd_cb_t cmd_cb, bt_data_cb_t data_cb);
void bt_sink_deinit(void);
/**
* @brief local command mode (stop, play, volume ...)
* @brief * @brief do what's necessary when becoming in charge
*/
void bt_sink_cmd(bt_sink_cmd_t event, ...);
void bt_master(bool on);
/**
* @brief force disconnection
*/
void bt_disconnect(void);
#endif /* __BT_APP_SINK_H__*/

View File

@@ -44,6 +44,7 @@
#include "log_util.h"
#define RTSP_STACK_SIZE (8*1024)
#define SEARCH_STACK_SIZE (2*1048)
typedef struct raop_ctx_s {
#ifdef WIN32
@@ -60,7 +61,7 @@ typedef struct raop_ctx_s {
#else
TaskHandle_t thread, search_thread, joiner;
StaticTask_t *xTaskBuffer;
StackType_t *xStack;
StackType_t xStack[RTSP_STACK_SIZE] __attribute__ ((aligned (4)));
#endif
unsigned char mac[6];
int latency;
@@ -71,14 +72,19 @@ typedef struct raop_ctx_s {
struct rtp_s *rtp;
raop_cmd_cb_t cmd_cb;
raop_data_cb_t data_cb;
/*
struct {
char DACPid[32], id[32];
struct in_addr host;
u16_t port;
#ifdef WIN32
struct mDNShandle_s *handle;
#else
bool running;
TaskHandle_t thread, joiner;
StaticTask_t *xTaskBuffer;
StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));;
#endif
} active_remote;
*/
void *owner;
} raop_ctx_t;
@@ -98,7 +104,7 @@ static void* search_remote(void *args);
extern char private_key[];
enum { RSA_MODE_KEY, RSA_MODE_AUTH };
static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len);
/*----------------------------------------------------------------------------*/
@@ -186,26 +192,23 @@ struct raop_ctx_s *raop_create(struct in_addr host, char *name,
id[63] = '\0';
ctx->svc = mdnsd_register_svc(ctx->svr, id, "_raop._tcp.local", ctx->port, NULL, (const char**) txt);
pthread_create(&ctx->thread, NULL, &rtsp_thread, ctx);
pthread_create(&ctx->thread, NULL, &rtsp_thread, ctx);
#else
LOG_INFO("starting mDNS with %s", id);
ESP_ERROR_CHECK( mdns_service_add(id, "_raop", "_tcp", ctx->port, txt, sizeof(txt) / sizeof(mdns_txt_item_t)) );
ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ctx->xStack = (StackType_t*) malloc(RTSP_STACK_SIZE);
ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtsp_thread, "RTSP_thread", RTSP_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->xStack, ctx->xTaskBuffer);
#endif
return ctx;
}
/*----------------------------------------------------------------------------*/
void raop_delete(struct raop_ctx_s *ctx) {
int sock;
/*----------------------------------------------------------------------------*/
void raop_delete(struct raop_ctx_s *ctx) {
#ifdef WIN32
int sock;
struct sockaddr addr;
socklen_t nlen = sizeof(struct sockaddr);
#endif
@@ -214,66 +217,99 @@ void raop_delete(struct raop_ctx_s *ctx) {
#ifdef WIN32
ctx->running = false;
// wake-up thread by connecting socket, needed for freeBSD
sock = socket(AF_INET, SOCK_STREAM, 0);
getsockname(ctx->sock, (struct sockaddr *) &addr, &nlen);
connect(sock, (struct sockaddr*) &addr, sizeof(addr));
closesocket(sock);
#ifdef WIN32
sock = socket(AF_INET, SOCK_STREAM, 0);
getsockname(ctx->sock, (struct sockaddr *) &addr, &nlen);
connect(sock, (struct sockaddr*) &addr, sizeof(addr));
vTaskDelete(ctx->thread);
closesocket(sock);
heap_caps_free(ctx->xTaskBuffer);
#endif
pthread_join(ctx->thread, NULL);
#ifdef WIN32
rtp_end(ctx->rtp);
shutdown(ctx->sock, SD_BOTH);
closesocket(ctx->sock);
// terminate search, but do not reclaim memory of pthread if never launched
if (ctx->active_remote.handle) {
close_mDNS(ctx->active_remote.handle);
pthread_join(ctx->search_thread, NULL);
}
// stop broadcasting devices
mdns_service_remove(ctx->svr, ctx->svc);
mdnsd_stop(ctx->svr);
#else
// first stop the search task if any
if (ctx->active_remote.running) {
ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
ctx->active_remote.running = false;
vTaskResume(ctx->active_remote.thread);
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->active_remote.thread);
heap_caps_free(ctx->active_remote.xTaskBuffer);
}
// then the RTSP task
ctx->joiner = xTaskGetCurrentTaskHandle();
ctx->running = false;
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->thread);
heap_caps_free(ctx->xTaskBuffer);
rtp_end(ctx->rtp);
shutdown(ctx->sock, SHUT_RDWR);
closesocket(ctx->sock);
mdns_service_remove("_raop", "_tcp");
}
*/
NFREE(ctx->rtsp.aeskey);
NFREE(ctx->rtsp.aesiv);
NFREE(ctx->rtsp.fmtp);
// stop broadcasting devices
#endif
NFREE(ctx->rtsp.aeskey);
#else
NFREE(ctx->rtsp.aesiv);
NFREE(ctx->rtsp.fmtp);
free(ctx);
}
/*----------------------------------------------------------------------------*/
void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
struct sockaddr_in addr;
int sock;
char *command = NULL;
// first notify the remote controller (if any)
switch(event) {
case RAOP_REW:
command = strdup("beginrew");
break;
case RAOP_FWD:
command = strdup("beginff");
break;
case RAOP_PREV:
command = strdup("previtem");
break;
case RAOP_NEXT:
command = strdup("nextitem");
break;
case RAOP_TOGGLE:
command = strdup("playpause");
break;
case RAOP_PAUSE:
command = strdup("pause");
break;
case RAOP_PLAY:
command = strdup("play");
break;
case RAOP_RESUME:
command = strdup("playresume");
break;
case RAOP_STOP:
command = strdup("stop");
break;
case RAOP_VOLUME_UP:
command = strdup("volumeup");
break;
@@ -317,11 +353,7 @@ void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
if (len > 0) resp[len-1] = '\0';
LOG_INFO("[%p]: sending airplay remote\n%s<== received ==>\n%s", ctx, buf, resp);
len = recv(sock, resp, 512, 0);
NFREE(method);
LOG_INFO("[%p]: sending airplay remote\n%s<== received ==>\n%s", ctx, buf, resp);
NFREE(method);
NFREE(buf);
kd_free(headers);
}
@@ -366,7 +398,7 @@ static void *rtsp_thread(void *arg) {
closesocket(sock);
LOG_INFO("RTSP close %u", sock);
sock = -1;
if (n < 0 || !res) {
}
}
if (sock != -1) closesocket(sock);
@@ -429,12 +461,27 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
kd_add(resp, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
} else if (!strcmp(method, "ANNOUNCE")) {
char *padded, *p;
NFREE(ctx->rtsp.aeskey);
NFREE(ctx->rtsp.aesiv);
NFREE(ctx->rtsp.fmtp);
// LMS might has taken over the player, leaving us with a running RTP session (should not happen)
if (ctx->rtp) {
LOG_WARN("[%p]: closing unfinished RTP session", ctx);
rtp_end(ctx->rtp);
}
// same, should not happen unless we have missed a teardown ...
if (ctx->active_remote.running) {
ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
ctx->active_remote.running = false;
vTaskResume(ctx->active_remote.thread);
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->active_remote.thread);
heap_caps_free(ctx->active_remote.xTaskBuffer);
memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
@@ -467,13 +514,17 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
}
if ((p = strcasestr(body, "fmtp")) != NULL) {
NFREE(padded);
p = strextract(p, ":", "\r\n");
ctx->rtsp.fmtp = strdup(p);
NFREE(p);
}
// on announce, search remote
NFREE(p);
if ((buf = kd_lookup(headers, "DACP-ID")) != NULL) strcpy(ctx->active_remote.DACPid, buf);
if ((buf = kd_lookup(headers, "Active-Remote")) != NULL) strcpy(ctx->active_remote.id, buf);
#ifdef WIN32
ctx->active_remote.handle = init_mDNS(false, ctx->host);
pthread_create(&ctx->search_thread, NULL, &search_remote, ctx);
#else
ctx->active_remote.running = true;
@@ -481,7 +532,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
ctx->active_remote.thread = xTaskCreateStatic( (TaskFunction_t) search_remote, "search_remote", SEARCH_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->active_remote.xStack, ctx->active_remote.xTaskBuffer);
#endif
ctx->active_remote.handle = init_mDNS(false, ctx->host);
} else if (!strcmp(method, "SETUP") && ((buf = kd_lookup(headers, "Transport")) != NULL)) {
char *p;
rtp_resp_t rtp = { 0 };
short unsigned tport = 0, cport = 0;
@@ -520,7 +571,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
kd_add(resp, "Audio-Latency", latency);
}
snprintf(latency, 6, "%u", ctx->latency);
buf = kd_lookup(headers, "RTP-Info");
if (buf && (p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
if (buf && (p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime);
@@ -533,7 +584,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
unsigned rtptime = 0;
char *p;
unsigned short seqno = 0;
buf = kd_lookup(headers, "RTP-Info");
if ((p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
if ((p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime);
@@ -541,20 +592,32 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
if (ctx->rtp && rtp_flush(ctx->rtp, seqno, rtptime))
success = ctx->cmd_cb(RAOP_FLUSH, NULL);
// only send FLUSH if useful (discards frames above buffer head and top)
} else if (!strcmp(method, "TEARDOWN")) {
rtp_end(ctx->rtp);
} else if (!strcmp(method, "TEARDOWN")) {
ctx->rtp = NULL;
// need to make sure no search is on-going and reclaim pthread memory
#ifdef WIN32
if (ctx->active_remote.handle) close_mDNS(ctx->active_remote.handle);
pthread_join(ctx->search_thread, NULL);
#else
ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
ctx->active_remote.running = false;
// task might not need to be resumed anyway
vTaskResume(ctx->active_remote.thread);
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->active_remote.thread);
heap_caps_free(ctx->active_remote.xTaskBuffer);
LOG_INFO("[%p]: mDNS search task terminated", ctx);
#endif
// need to make sure no search is on-going and reclaim pthread memory
memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
pthread_join(ctx->search_thread, NULL);
NFREE(ctx->rtsp.aeskey);
NFREE(ctx->rtsp.aesiv);
NFREE(ctx->rtsp.fmtp);
@@ -563,7 +626,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
} else if (!strcmp(method, "SET_PARAMETER")) {
char *p;
if (body && (p = strcasestr(body, "volume")) != NULL) {
float volume;
sscanf(p, "%*[^:]:%f", &volume);
@@ -585,7 +648,6 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
LOG_INFO("[%p]: received metadata\n\tartist: %s\n\talbum: %s\n\ttitle: %s",
ctx, metadata.artist, metadata.album, metadata.title);
free_metadata(&metadata);
if (!dmap_parse(&settings, body, len)) {
}
}
*/
@@ -605,7 +667,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
NFREE(body);
NFREE(buf);
kd_free(resp);
kd_free(headers);
return true;
}
@@ -625,11 +687,9 @@ bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) {
inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
*stop = true;
break;
LOG_INFO("[%p]: found ActiveRemote for %s at %s:%u", ctx, ctx->active_remote.DACPid,
}
}
}
// let caller clear list
return false;
}
@@ -637,7 +697,57 @@ static void* search_remote(void *args) {
/*----------------------------------------------------------------------------*/
static void* search_remote(void *args) {
raop_ctx_t *ctx = (raop_ctx_t*) args;
query_mDNS(ctx->active_remote.handle, "_dacp._tcp.local", 0, 0, &search_remote_cb, (void*) ctx);
return NULL;
}
#else
/*----------------------------------------------------------------------------*/
static void* search_remote(void *args) {
raop_ctx_t *ctx = (raop_ctx_t*) args;
bool found = false;
LOG_INFO("starting remote search");
while (ctx->active_remote.running && !found) {
mdns_result_t *results = NULL;
mdns_result_t *r;
mdns_ip_addr_t *a;
if (mdns_query_ptr("_dacp", "_tcp", 3000, 32, &results)) {
LOG_ERROR("mDNS active remote query Failed");
continue;
}
for (r = results; r && !strcasestr(r->instance_name, ctx->active_remote.DACPid); r = r->next);
if (r) {
for (a = r->addr; a && a->addr.type != IPADDR_TYPE_V4; a = a->next);
if (a) {
found = true;
ctx->active_remote.host.s_addr = a->addr.u_addr.ip4.addr;
ctx->active_remote.port = r->port;
LOG_INFO("found remote %s %s:%hu", r->instance_name, inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
}
}
mdns_query_results_free(results);
}
/*
for some reason which is beyond me, if that tasks gives the semaphore
before the RTSP tasks waits for it, then freeRTOS crashes in queue
management caused by LWIP stack once the RTSP socket is closed. I have
no clue why, but so we'll suspend the tasks as soon as we're done with
search and wait for the resume then give the semaphore
*/
// PS: I know this is not fully race-condition free
if (ctx->active_remote.running) vTaskSuspend(NULL);
xTaskNotifyGive(ctx->active_remote.joiner);
// now our context will be deleted
vTaskSuspend(NULL);

View File

@@ -13,6 +13,7 @@
#include "freertos/timers.h"
#include "nvs_utilities.h"
#include "raop.h"
#include "audio_controls.h"
#include "log_util.h"
@@ -22,14 +23,68 @@
#define CONFIG_AIRPLAY_NAME "ESP32-AirPlay"
#endif
static const char * TAG = "platform";
log_level raop_loglevel = lINFO;
log_level util_loglevel;
static log_level *loglevel = &raop_loglevel;
static struct raop_ctx_s *raop;
static void raop_volume_up(void) {
raop_cmd(raop, RAOP_VOLUME_UP, NULL);
LOG_INFO("AirPlay volume up");
}
static void raop_volume_down(void) {
raop_cmd(raop, RAOP_VOLUME_DOWN, NULL);
LOG_INFO("AirPlay volume down");
}
static void raop_toggle(void) {
raop_cmd(raop, RAOP_TOGGLE, NULL);
LOG_INFO("AirPlay play/pause");
}
static void raop_pause(void) {
raop_cmd(raop, RAOP_PAUSE, NULL);
LOG_INFO("AirPlay pause");
}
static void raop_play(void) {
raop_cmd(raop, RAOP_PLAY, NULL);
LOG_INFO("AirPlay play");
}
static void raop_stop(void) {
raop_cmd(raop, RAOP_STOP, NULL);
LOG_INFO("AirPlay stop");
}
static void raop_prev(void) {
raop_cmd(raop, RAOP_PREV, NULL);
LOG_INFO("AirPlay previous");
}
static void raop_next(void) {
raop_cmd(raop, RAOP_NEXT, NULL);
LOG_INFO("AirPlay next");
}
const static actrls_t controls = {
raop_volume_up, raop_volume_down, // volume up, volume down
raop_toggle, raop_play, // toggle, play
raop_pause, raop_stop, // pause, stop
NULL, NULL, // rew, fwd
raop_prev, raop_next, // prev, next
};
/****************************************************************************************
* Airplay taking/giving audio system's control
*/
void raop_master(bool on) {
if (on) actrls_set(controls);
else actrls_unset();
}
/****************************************************************************************
* Airplay sink de-initialization
*/
@@ -56,14 +111,14 @@ void raop_sink_init(raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb) {
ESP_ERROR_CHECK( mdns_init() );
ESP_ERROR_CHECK( mdns_hostname_set(hostname) );
char * sink_name_buffer= (char *)config_alloc_get(NVS_TYPE_STR, "airplay_name");
char * sink_name_buffer= (char *)config_alloc_get(NVS_TYPE_STR,"AirPlay_name");
if(sink_name_buffer != NULL){
memset(sink_name, 0x00, sizeof(sink_name));
strncpy(sink_name,sink_name_buffer,sizeof(sink_name)-1 );
free(sink_name_buffer);
}
ESP_LOGI(TAG, "mdns hostname set to: [%s] with servicename %s", hostname, sink_name);
LOG_INFO( "mdns hostname set to: [%s] with servicename %s", hostname, sink_name);
// create RAOP instance, latency is set by controller
uint8_t mac[6];
@@ -72,8 +127,10 @@ void raop_sink_init(raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb) {
}
/****************************************************************************************
* Airplay local command (stop, start, volume ...)
* Airplay forced disconnection
*/
void raop_sink_cmd(raop_event_t event, void *param) {
raop_cmd(raop, event, param);
void raop_disconnect(void) {
LOG_INFO("forced disconnection");
raop_cmd(raop, RAOP_STOP, NULL);
actrls_unset();
}

View File

@@ -13,27 +13,31 @@
#define RAOP_SAMPLE_RATE 44100
typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_PAUSE, RAOP_STOP, RAOP_VOLUME, RAOP_TIMING } raop_event_t ;
typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_PAUSE, RAOP_STOP,
RAOP_VOLUME, RAOP_TIMING, RAOP_PREV, RAOP_NEXT, RAOP_REW, RAOP_FWD,
RAOP_VOLUME_UP, RAOP_VOLUME_DOWN, RAOP_RESUME, RAOP_TOGGLE } raop_event_t ;
typedef void (*raop_cmd_cb_t)(raop_event_t event, void *param);
typedef bool (*raop_cmd_cb_t)(raop_event_t event, void *param);
typedef void (*raop_data_cb_t)(const u8_t *data, size_t len, u32_t playtime);
/**
* @brief init sink mode (need to be provided)
*/
void raop_sink_init(raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb);
/**
* @brief deinit sink mode (need to be provided)
*/
void raop_sink_deinit(void);
/**
* @brief init sink mode (need to be provided)
* @brief do what's necessary when becoming in charge
*/
void raop_master(bool on);
void raop_sink_cmd(raop_event_t event, void *param);
/**
* @brief force disconnection
*/
void raop_disconnect(void);
#endif /* RAOP_SINK_H*/

View File

@@ -139,7 +139,7 @@ typedef struct rtp_s {
#ifdef WIN32
pthread_t thread;
#else
TaskHandle_t thread, joiner;
TaskHandle_t thread, joiner;
StaticTask_t *xTaskBuffer;
StackType_t xStack[RTP_STACK_SIZE] __attribute__ ((aligned (4)));
#endif
@@ -275,11 +275,11 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv,
#ifdef WIN32
pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx);
#else
// xTaskCreate((TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_TASK_SIZE, ctx, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1 , &ctx->thread);
// xTaskCreate((TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_TASK_SIZE, ctx, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1 , &ctx->thread);
ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_STACK_SIZE, ctx,
CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer );
#endif
} else {
LOG_ERROR("[%p]: cannot start RTP", ctx);
rtp_end(ctx);
@@ -304,9 +304,8 @@ void rtp_end(rtp_t *ctx)
#endif
ctx->running = false;
#ifdef WIN32
pthread_join(ctx->thread, NULL);
pthread_join(ctx->thread, NULL);
#else
xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->thread);
heap_caps_free(ctx->xTaskBuffer);
@@ -715,7 +714,7 @@ static void *rtp_thread_func(void *arg) {
}
free(packet);
LOG_INFO("[%p]: terminating", ctx);
LOG_INFO("[%p]: terminating", ctx);
#ifndef WIN32
xTaskNotifyGive(ctx->joiner);

View File

@@ -69,7 +69,7 @@ static void lms_next(void) {
cli_send_cmd("button fwd");
}
static actrls_t controls = {
const static actrls_t controls = {
lms_volume_up, lms_volume_down, // volume up, volume down
lms_toggle, lms_play, // toggle, play
lms_pause, lms_stop, // pause, stop
@@ -102,7 +102,7 @@ static void cli_send_cmd(char *cmd) {
LOG_WARN("cannot send CLI %s", packet);
}
close(sock);
closesocket(sock);
}
/****************************************************************************************

View File

@@ -65,7 +65,7 @@ static void *decode_thread() {
toend = (stream.state <= DISCONNECT);
UNLOCK_S;
LOCK_O;
space = _buf_space(outputbuf);
space = !output.external ? _buf_space(outputbuf) : 0;
UNLOCK_O;
LOCK_D;
@@ -117,10 +117,6 @@ static void *decode_thread() {
}
}
#if EMBEDDED
deregister_external();
#endif
return 0;
}
@@ -246,6 +242,9 @@ void decode_close(void) {
pthread_join(thread, NULL);
#endif
mutex_destroy(decode.mutex);
#if EMBEDDED
deregister_external();
#endif
}
void decode_flush(void) {

View File

@@ -29,7 +29,7 @@
#define LOCK_D mutex_lock(decode.mutex);
#define UNLOCK_D mutex_unlock(decode.mutex);
enum { DECODE_BT = 1, DECODE_AIRPLAY };
enum { DECODE_BT = 1, DECODE_RAOP };
extern struct outputstate output;
extern struct decodestate decode;
@@ -60,8 +60,8 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
{
size_t bytes, space;
// would be better to lock decoder, but really, it does not matter
if (decode.state != DECODE_STOPPED) {
// would be better to lock output, but really, it does not matter
if (!output.external) {
LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
return;
}
@@ -99,19 +99,18 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
* BT sink command handler
*/
static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...)
static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...)
{
va_list args;
LOCK_D;
if (decode.state != DECODE_STOPPED) {
LOG_WARN("Cannot use BT sink while LMS is controlling player");
UNLOCK_D;
bt_sink_cmd(BT_SINK_DISCONNECTED);
return;
// don't LOCK_O as there is always a chance that LMS takes control later anyway
if (output.external != DECODE_BT && output.state > OUTPUT_STOPPED) {
LOG_WARN("Cannot use BT sink while LMS/AirPlay is controlling player");
return false;
}
LOCK_D;
va_start(args, cmd);
if (cmd != BT_SINK_VOLUME) LOCK_O;
@@ -120,12 +119,15 @@ static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...)
case BT_SINK_CONNECTED:
output.external = DECODE_BT;
output.state = OUTPUT_STOPPED;
output.frames_played = 0;
_buf_flush(outputbuf);
bt_master(true);
LOG_INFO("BT sink started");
break;
case BT_SINK_DISCONNECTED:
if (output.external == DECODE_BT) {
output.external = 0;
output.state = OUTPUT_OFF;
bt_master(false);
LOG_INFO("BT sink stopped");
}
break;
@@ -155,6 +157,7 @@ static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...)
UNLOCK_D;
va_end(args);
return true;
}
/****************************************************************************************
@@ -171,16 +174,16 @@ static void raop_sink_data_handler(const uint8_t *data, uint32_t len, u32_t play
/****************************************************************************************
* AirPlay sink command handler
*/
void raop_sink_cmd_handler(raop_event_t event, void *param)
static bool raop_sink_cmd_handler(raop_event_t event, void *param)
{
LOCK_D;
if (decode.state != DECODE_STOPPED) {
LOG_WARN("Cannot use Airplay sink while LMS is controlling player");
UNLOCK_D;
return;
// don't LOCK_O as there is always a chance that LMS takes control later anyway
if (output.external != DECODE_RAOP && output.state > OUTPUT_STOPPED) {
LOG_WARN("Cannot use Airplay sink while LMS/BT is controlling player");
return false;
}
LOCK_D;
if (event != RAOP_VOLUME) LOCK_O;
// this is async, so player might have been deleted
@@ -233,6 +236,10 @@ void raop_sink_cmd_handler(raop_event_t event, void *param)
case RAOP_SETUP:
// we need a fair bit of space for RTP process
_buf_resize(outputbuf, RAOP_OUTPUT_SIZE);
output.frames_played = 0;
output.external = DECODE_RAOP;
output.state = OUTPUT_STOPPED;
raop_master(true);
LOG_INFO("resizing buffer %u", outputbuf->size);
break;
case RAOP_STREAM:
@@ -242,16 +249,14 @@ void raop_sink_cmd_handler(raop_event_t event, void *param)
raop_sync.idx = 0;
raop_sync.start = true;
raop_sync.enabled = !strcasestr(output.device, "BT");
output.external = DECODE_AIRPLAY;
output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
output.state = OUTPUT_STOPPED;
break;
case RAOP_STOP:
LOG_INFO("Stop", NULL);
output.external = 0;
output.state = OUTPUT_OFF;
output.frames_played = 0;
raop_state = event;
raop_master(false);
break;
case RAOP_FLUSH:
LOG_INFO("Flush", NULL);
@@ -286,6 +291,7 @@ void raop_sink_cmd_handler(raop_event_t event, void *param)
if (event != RAOP_VOLUME) UNLOCK_O;
UNLOCK_D;
return true;
}
/****************************************************************************************
@@ -300,7 +306,7 @@ void register_external(void) {
} else {
LOG_WARN("Cannot be a BT sink and source");
}
if(enable_airplay){
if (enable_airplay){
raop_sink_init(raop_sink_cmd_handler, raop_sink_data_handler);
LOG_INFO("Initializing AirPlay sink");
}
@@ -311,8 +317,21 @@ void deregister_external(void) {
bt_sink_deinit();
LOG_INFO("Stopping BT sink");
}
if(enable_airplay){
if (enable_airplay){
raop_sink_deinit();
LOG_INFO("Stopping AirPlay sink");
}
}
void decode_resume(int external) {
switch (external) {
case DECODE_BT:
bt_disconnect();
break;
case DECODE_RAOP:
raop_disconnect();
raop_state = RAOP_STOP;
break;
}
_buf_resize(outputbuf, output.init_size);
}

View File

@@ -48,3 +48,9 @@ int pthread_create_name(pthread_t *thread, _CONST pthread_attr_t *attr,
uint32_t _gettime_ms_(void) {
return (uint32_t) (esp_timer_get_time() / 1000);
}
extern void cli_controls_init(void);
void embedded_init(void) {
cli_controls_init();
}

View File

@@ -43,8 +43,11 @@ uint32_t _gettime_ms_(void);
int pthread_create_name(pthread_t *thread, _CONST pthread_attr_t *attr,
void *(*start_routine)( void * ), void *arg, char *name);
void embedded_init(void);
void register_external(void);
void deregister_external(void);
void decode_resume(int external);
void (*server_notify)(in_addr_t ip, u16_t hport, u16_t cport);
#endif // EMBEDDED_H

View File

@@ -761,6 +761,7 @@ int main(int argc, char **argv) {
stream_init(log_stream, stream_buf_size);
#if EMBEDDED
embedded_init();
output_init_embedded(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
#else
if (!strcmp(output_device, "-")) {

View File

@@ -57,8 +57,6 @@ void output_init_embedded(log_level level, char *device, unsigned output_buf_siz
output.start_frames = FRAME_BLOCK;
output.rate_delay = rate_delay;
cli_controls_init();
if (strcasestr(device, "BT ")) {
LOG_INFO("init Bluetooth");
close_cb = &output_close_bt;

View File

@@ -330,6 +330,12 @@ static void process_strm(u8_t *pkt, int len) {
LOCK_O;
output.state = jiffies ? OUTPUT_START_AT : OUTPUT_RUNNING;
output.start_at = jiffies;
#if EMBEDDED
if (output.external) {
decode_resume(output.external);
output.external = 0;
}
#endif
UNLOCK_O;
LOG_DEBUG("unpause at: %u now: %u", jiffies, gettime_ms());
@@ -373,8 +379,10 @@ static void process_strm(u8_t *pkt, int len) {
sendSTAT("STMc", 0);
sentSTMu = sentSTMo = sentSTMl = false;
LOCK_O;
#if EMBEDDED
if (output.external) decode_resume(output.external);
output.external = 0;
_buf_resize(outputbuf, output.init_size);
#endif
output.threshold = strm->output_threshold;
output.next_replay_gain = unpackN(&strm->replay_gain);
output.fade_mode = strm->transition_type - '0';