diff --git a/components/audio_controls/audio_controls.c b/components/audio_controls/audio_controls.c index 925e1ae9..1e5a1cb5 100644 --- a/components/audio_controls/audio_controls.c +++ b/components/audio_controls/audio_controls.c @@ -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)); } diff --git a/components/audio_controls/audio_controls.h b/components/audio_controls/audio_controls.h index f541f30f..6db3d890 100644 --- a/components/audio_controls/audio_controls.h +++ b/components/audio_controls/audio_controls.h @@ -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); diff --git a/components/audio_controls/buttons.c b/components/audio_controls/buttons.c index c66cfbb8..821af4c2 100644 --- a/components/audio_controls/buttons.c +++ b/components/audio_controls/buttons.c @@ -36,10 +36,11 @@ static const char * TAG = "audio_controls"; static int n_buttons = 0; -#define MAX_BUTTONS 16 -#define DEBOUNCE 50 +#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; @@ -95,7 +96,7 @@ static void buttons_timer( TimerHandle_t xTimer ) { */ static void buttons_task(void* arg) { ESP_LOGI(TAG, "starting button tasks"); - + while (1) { struct button_s button; button_event_e event; @@ -149,7 +150,9 @@ 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; ESP_LOGI(TAG, "creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %u", gpio, type, pull, long_press, shifter_gpio); @@ -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 diff --git a/components/driver_bt/bt_app_sink.c b/components/driver_bt/bt_app_sink.c index 0cd68f05..3dfbb1b8 100644 --- a/components/driver_bt/bt_app_sink.c +++ b/components/driver_bt/bt_app_sink.c @@ -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: - esp_a2d_sink_disconnect(s_remote_bda); - actrls_unset(); - break; - default: - ESP_LOGW(BT_AV_TAG, "unhandled command %u", event); - break; - } +/* 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(); + 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; } diff --git a/components/driver_bt/bt_app_sink.h b/components/driver_bt/bt_app_sink.h index aa641bc8..33e77195 100644 --- a/components/driver_bt/bt_app_sink.h +++ b/components/driver_bt/bt_app_sink.h @@ -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__*/ diff --git a/components/raop/raop.c b/components/raop/raop.c index 83f0ac9b..69cccc26 100644 --- a/components/raop/raop.c +++ b/components/raop/raop.c @@ -43,7 +43,8 @@ #include "dmap_parser.h" #include "log_util.h" -#define RTSP_STACK_SIZE (8*1024) +#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); +static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len); /*----------------------------------------------------------------------------*/ struct raop_ctx_s *raop_create(struct in_addr host, char *name, @@ -186,26 +192,23 @@ struct raop_ctx_s *raop_create(struct in_addr host, char *name, 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) { +#ifdef WIN32 int sock; struct sockaddr addr; socklen_t nlen = sizeof(struct sockaddr); - - if (!ctx) return; - -#if !defined WIN32 - ctx->joiner = xTaskGetCurrentTaskHandle(); #endif + +if (!ctx) return; +#ifdef WIN32 ctx->running = false; // wake-up thread by connecting socket, needed for freeBSD @@ -214,66 +217,99 @@ void raop_delete(struct raop_ctx_s *ctx) { connect(sock, (struct sockaddr*) &addr, sizeof(addr)); closesocket(sock); -#ifdef WIN32 pthread_join(ctx->thread, NULL); -#else - xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); - vTaskDelete(ctx->thread); - free(ctx->xStack); - heap_caps_free(ctx->xTaskBuffer); -#endif rtp_end(ctx->rtp); -#ifdef WIN32 shutdown(ctx->sock, SD_BOTH); -#else - shutdown(ctx->sock, SHUT_RDWR); -#endif 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"); +#endif NFREE(ctx->rtsp.aeskey); NFREE(ctx->rtsp.aesiv); NFREE(ctx->rtsp.fmtp); - - // stop broadcasting devices -#ifdef WIN32 - mdns_service_remove(ctx->svr, ctx->svc); - mdnsd_stop(ctx->svr); -#else - mdns_service_remove("_raop", "_tcp"); -#endif free(ctx); } - /*----------------------------------------------------------------------------*/ -void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) { -/* +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; + case RAOP_VOLUME_DOWN: + command = strdup("volumedown"); + break; case RAOP_VOLUME: { float Volume = *((float*) param); Volume = Volume ? (Volume - 1) * 30 : -144; @@ -286,9 +322,9 @@ void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) { // no command to send to remote or no remote found yet if (!command || !ctx->active_remote.port) { - NFREE(command); - return; - } + NFREE(command); + return; + } sock = socket(AF_INET, SOCK_STREAM, 0); @@ -317,11 +353,7 @@ void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) { } free(command); - closesocket(sock); -*/ - // then notify local system - ctx->cmd_cb(event, param); } /*----------------------------------------------------------------------------*/ @@ -366,7 +398,7 @@ static void *rtsp_thread(void *arg) { if (sock != -1) closesocket(sock); #ifndef WIN32 - xTaskNotify(ctx->joiner, 0, eNoAction); + xTaskNotifyGive(ctx->joiner); vTaskSuspend(NULL); #endif @@ -429,12 +461,27 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) NFREE(ctx->rtsp.aesiv); NFREE(ctx->rtsp.fmtp); - // LMS might has taken over the player, leaving us with a running RTP session + // LMS might has taken over the player, leaving us with a running RTP session (should not happen) if (ctx->rtp) { - LOG_INFO("[%p]: closing unfinished RTP session", ctx); + 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)); + + LOG_WARN("[%p]: closing unfinished mDNS search", ctx); + } + if ((p = strcasestr(body, "rsaaeskey")) != NULL) { unsigned char *aeskey; int len, outlen; @@ -467,13 +514,17 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) } // on announce, search remote - /* 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; + ctx->active_remote.xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + 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 } else if (!strcmp(method, "SETUP") && ((buf = kd_lookup(headers, "Transport")) != NULL)) { char *p; @@ -481,7 +532,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) short unsigned tport = 0, cport = 0; // we are about to stream, do something if needed - ctx->cmd_cb(RAOP_SETUP, NULL); + success = ctx->cmd_cb(RAOP_SETUP, NULL); if ((p = strcasestr(buf, "timing_port")) != NULL) sscanf(p, "%*[^=]=%hu", &tport); if ((p = strcasestr(buf, "control_port")) != NULL) sscanf(p, "%*[^=]=%hu", &cport); @@ -520,7 +571,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) if (ctx->rtp) rtp_record(ctx->rtp, seqno, rtptime); - ctx->cmd_cb(RAOP_STREAM, NULL); + success = ctx->cmd_cb(RAOP_STREAM, NULL); } else if (!strcmp(method, "FLUSH")) { unsigned short seqno = 0; @@ -533,7 +584,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) // only send FLUSH if useful (discards frames above buffer head and top) if (ctx->rtp && rtp_flush(ctx->rtp, seqno, rtptime)) - ctx->cmd_cb(RAOP_FLUSH, NULL); + success = ctx->cmd_cb(RAOP_FLUSH, NULL); } else if (!strcmp(method, "TEARDOWN")) { @@ -541,20 +592,32 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) 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); - memset(&ctx->active_remote, 0, sizeof(ctx->active_remote)); - */ +#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 + + memset(&ctx->active_remote, 0, sizeof(ctx->active_remote)); NFREE(ctx->rtsp.aeskey); NFREE(ctx->rtsp.aesiv); NFREE(ctx->rtsp.fmtp); - ctx->cmd_cb(RAOP_STOP, NULL); + success = ctx->cmd_cb(RAOP_STOP, NULL); - } if (!strcmp(method, "SET_PARAMETER")) { + } else if (!strcmp(method, "SET_PARAMETER")) { char *p; if (body && (p = strcasestr(body, "volume")) != NULL) { @@ -563,7 +626,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) sscanf(p, "%*[^:]:%f", &volume); LOG_INFO("[%p]: SET PARAMETER volume %f", ctx, volume); volume = (volume == -144.0) ? 0 : (1 + volume / 30); - ctx->cmd_cb(RAOP_VOLUME, &volume); + success = ctx->cmd_cb(RAOP_VOLUME, &volume); } /* if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && !strcasecmp(p, "application/x-dmap-tagged")) { @@ -585,7 +648,6 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) } // don't need to free "buf" because kd_lookup return a pointer, not a strdup - kd_add(resp, "Audio-Jack-Status", "connected; type=analog"); kd_add(resp, "CSeq", kd_lookup(headers, "CSeq")); @@ -605,7 +667,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) } /*----------------------------------------------------------------------------*/ -/* +#ifdef WIN32 bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) { mDNSservice_t *s; raop_ctx_t *ctx = (raop_ctx_t*) cookie; @@ -625,11 +687,9 @@ bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) { // let caller clear list return false; } -*/ /*----------------------------------------------------------------------------*/ -/* static void* search_remote(void *args) { raop_ctx_t *ctx = (raop_ctx_t*) args; @@ -637,8 +697,58 @@ static void* search_remote(void *args) { 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); + + return NULL; + } +#endif + /*----------------------------------------------------------------------------*/ static char *rsa_apply(unsigned char *input, int inlen, int *outlen, int mode) { diff --git a/components/raop/raop.h b/components/raop/raop.h index 706fa8d9..5c6d0acf 100644 --- a/components/raop/raop.h +++ b/components/raop/raop.h @@ -24,7 +24,7 @@ #include "platform.h" #include "raop_sink.h" -struct raop_ctx_s* raop_create(struct in_addr host, char *name, unsigned char mac[6], int latency, +struct raop_ctx_s* raop_create(struct in_addr host, char *name, unsigned char mac[6], int latency, raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb); void raop_delete(struct raop_ctx_s *ctx); void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param); diff --git a/components/raop/raop_sink.c b/components/raop/raop_sink.c index 86512984..ff1ee484 100644 --- a/components/raop/raop_sink.c +++ b/components/raop/raop_sink.c @@ -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(); } diff --git a/components/raop/raop_sink.h b/components/raop/raop_sink.h index bfe5e430..66d9bb84 100644 --- a/components/raop/raop_sink.h +++ b/components/raop/raop_sink.h @@ -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*/ \ No newline at end of file diff --git a/components/raop/rtp.c b/components/raop/rtp.c index b4661a98..7cc57e1a 100644 --- a/components/raop/rtp.c +++ b/components/raop/rtp.c @@ -139,7 +139,7 @@ typedef struct rtp_s { #else TaskHandle_t thread, joiner; StaticTask_t *xTaskBuffer; - StackType_t *xStack; + StackType_t xStack[RTP_STACK_SIZE] __attribute__ ((aligned (4))); #endif struct alac_codec_s *alac_codec; @@ -275,11 +275,11 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv, #else // 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->xStack = (StackType_t*) malloc(RTP_STACK_SIZE); 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); ctx = NULL; } @@ -304,9 +304,8 @@ void rtp_end(rtp_t *ctx) #ifdef WIN32 pthread_join(ctx->thread, NULL); #else - xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); + ulTaskNotifyTake(pdFALSE, portMAX_DELAY); vTaskDelete(ctx->thread); - free(ctx->xStack); heap_caps_free(ctx->xTaskBuffer); #endif } @@ -715,7 +714,7 @@ static void *rtp_thread_func(void *arg) { LOG_INFO("[%p]: terminating", ctx); #ifndef WIN32 - xTaskNotify(ctx->joiner, 0, eNoAction); + xTaskNotifyGive(ctx->joiner); vTaskSuspend(NULL); #endif diff --git a/components/squeezelite/controls.c b/components/squeezelite/controls.c index 8f0332a1..924541c4 100644 --- a/components/squeezelite/controls.c +++ b/components/squeezelite/controls.c @@ -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); } /**************************************************************************************** diff --git a/components/squeezelite/decode.c b/components/squeezelite/decode.c index cc59f77a..de208e2d 100644 --- a/components/squeezelite/decode.c +++ b/components/squeezelite/decode.c @@ -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) { diff --git a/components/squeezelite/decode_external.c b/components/squeezelite/decode_external.c index e8adec3e..98028efe 100644 --- a/components/squeezelite/decode_external.c +++ b/components/squeezelite/decode_external.c @@ -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,15 +174,15 @@ 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; @@ -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); +} diff --git a/components/squeezelite/embedded.c b/components/squeezelite/embedded.c index e2379b98..a46522c0 100644 --- a/components/squeezelite/embedded.c +++ b/components/squeezelite/embedded.c @@ -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(); +} diff --git a/components/squeezelite/embedded.h b/components/squeezelite/embedded.h index a6442ade..ecc526eb 100644 --- a/components/squeezelite/embedded.h +++ b/components/squeezelite/embedded.h @@ -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 diff --git a/components/squeezelite/main.c b/components/squeezelite/main.c index 9b1f8f1b..38be869d 100644 --- a/components/squeezelite/main.c +++ b/components/squeezelite/main.c @@ -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, "-")) { diff --git a/components/squeezelite/output_embedded.c b/components/squeezelite/output_embedded.c index dbb12218..c9073458 100644 --- a/components/squeezelite/output_embedded.c +++ b/components/squeezelite/output_embedded.c @@ -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; diff --git a/components/squeezelite/slimproto.c b/components/squeezelite/slimproto.c index c31bd0f5..23c96e4d 100644 --- a/components/squeezelite/slimproto.c +++ b/components/squeezelite/slimproto.c @@ -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';