From 3a02b79a622e96ee6cc0054480e460075640229d Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 30 Apr 2021 09:29:30 +0100 Subject: [PATCH] Update the mdns stuff to work with a string, update audio_alsa rtp.c, rtsp.c and shairport.c to build and work in AirPlay 1 but with AirPlay 2 code in place. Some of it is #defined out. --- audio_alsa.c | 2 +- mdns.c | 9 +- mdns.h | 4 +- mdns_avahi.c | 56 +++++----- mdns_dns_sd.c | 3 +- mdns_external.c | 6 +- mdns_tinysvcmdns.c | 3 +- rtp.c | 2 +- rtsp.c | 247 +++++++++++++++++++++++++++++++++------------ rtsp.h | 3 +- shairport.c | 138 ++++++++++++++++++++++--- 11 files changed, 356 insertions(+), 117 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index b464279c..19127d11 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1771,7 +1771,7 @@ int do_close() { // debug(1,"alsa: do_close() -- closing the output device"); if ((derr = snd_pcm_drop(alsa_handle))) debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr)); - usleep(5000); + usleep(5000); // this make the function pthread cancellable if ((derr = snd_pcm_hw_free(alsa_handle))) debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); diff --git a/mdns.c b/mdns.c index 0e305c00..7eb1f433 100644 --- a/mdns.c +++ b/mdns.c @@ -1,6 +1,7 @@ /* * mDNS registration handler. This file is part of Shairport. * Copyright (c) James Laird 2013 + * Modifications, updates and additions (c) Mike Brady 2014 -- 2020 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -63,15 +64,17 @@ static mdns_backend *mdns_backends[] = { #endif NULL}; -void mdns_register(void) { +void mdns_register(char **txt_records) { char *mdns_service_name = alloca(strlen(config.service_name) + 14); char *p = mdns_service_name; +#ifndef CONFIG_AIRPLAY_2 int i; for (i = 0; i < 6; i++) { snprintf(p, 3, "%02X", config.hw_addr[i]); p += 2; } *p++ = '@'; +#endif strcpy(p, config.service_name); mdns_backend **b = NULL; @@ -80,7 +83,7 @@ void mdns_register(void) { for (b = mdns_backends; *b; b++) { if (strcmp((*b)->name, config.mdns_name) != 0) // Not the one we are looking for continue; - int error = (*b)->mdns_register(mdns_service_name, config.port); + int error = (*b)->mdns_register(mdns_service_name, config.port, txt_records); if (error >= 0) { config.mdns = *b; } @@ -91,7 +94,7 @@ void mdns_register(void) { warn("%s mDNS backend not found"); } else { for (b = mdns_backends; *b; b++) { - int error = (*b)->mdns_register(mdns_service_name, config.port); + int error = (*b)->mdns_register(mdns_service_name, config.port, txt_records); if (error >= 0) { config.mdns = *b; break; diff --git a/mdns.h b/mdns.h index b69f4c85..73c15e8e 100644 --- a/mdns.h +++ b/mdns.h @@ -8,7 +8,7 @@ extern int mdns_pid; void mdns_unregister(void); -void mdns_register(void); +void mdns_register(char **txt_records); void mdns_dacp_monitor_start(); void mdns_dacp_monitor_stop(void); void mdns_dacp_monitor_set_id(const char *dacp_id); @@ -17,7 +17,7 @@ void mdns_ls_backends(void); typedef struct { char *name; - int (*mdns_register)(char *apname, int port); + int (*mdns_register)(char *apname, int port, char **txt_records); void (*mdns_unregister)(void); void (*mdns_dacp_monitor_start)(); void (*mdns_dacp_monitor_set_id)(const char *); diff --git a/mdns_avahi.c b/mdns_avahi.c index 47ff8a1d..4ab991c7 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -61,6 +61,8 @@ typedef struct { dacp_browser_struct private_dbs; +AvahiStringList *text_record_string_list = NULL; + // static AvahiServiceBrowser *sb = NULL; static AvahiClient *client = NULL; // static AvahiClient *service_client = NULL; @@ -229,36 +231,29 @@ static void register_service(AvahiClient *c) { if (!avahi_entry_group_is_empty(group)) return; - int ret; + int ret = 0; + AvahiIfIndex selected_interface; if (config.interface != NULL) selected_interface = config.interface_index; else selected_interface = AVAHI_IF_UNSPEC; -#ifdef CONFIG_METADATA - if (config.metadata_enabled) { - ret = avahi_entry_group_add_service(group, selected_interface, AVAHI_PROTO_UNSPEC, 0, - service_name, config.regtype, NULL, NULL, port, - MDNS_RECORD_WITH_METADATA, NULL); - if (ret == 0) - debug(2, "avahi: request to add \"%s\" service with metadata", config.regtype); + if (text_record_string_list) { + ret = avahi_entry_group_add_service_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0, + service_name, config.regtype, NULL, NULL, port, + text_record_string_list); + if (ret == 0) { + ret = avahi_entry_group_commit(group); + debug(2, "avahi: avahi_entry_group_commit %d", ret); + if (ret < 0) + debug(1, "avahi: avahi_entry_group_commit failed"); + } else if (ret < 0) { + debug(1, "avahi: avahi_entry_group_add_service failed"); + } else { + debug(1, "avahi: unexpected positive return"); + } } else { -#endif - ret = avahi_entry_group_add_service(group, selected_interface, AVAHI_PROTO_UNSPEC, 0, - service_name, config.regtype, NULL, NULL, port, - MDNS_RECORD_WITHOUT_METADATA, NULL); - if (ret == 0) - debug(2, "avahi: request to add \"%s\" service without metadata", config.regtype); -#ifdef CONFIG_METADATA - } -#endif - - if (ret < 0) - debug(1, "avahi: avahi_entry_group_add_service failed"); - else { - ret = avahi_entry_group_commit(group); - if (ret < 0) - debug(1, "avahi: avahi_entry_group_commit failed"); + debug(1, "Can't find a valid text_record_string_list"); } } } @@ -317,9 +312,12 @@ static void client_callback(AvahiClient *c, AvahiClientState state, } } -static int avahi_register(char *srvname, int srvport) { +static int avahi_register(char *srvname, int srvport, char **txt_records) { // debug(1, "avahi_register."); service_name = strdup(srvname); + + text_record_string_list = avahi_string_list_new_from_array((const char **)txt_records, -1); + port = srvport; int err; @@ -342,7 +340,7 @@ static int avahi_register(char *srvname, int srvport) { } static void avahi_unregister(void) { - // debug(1, "avahi_unregister."); + debug(2, "avahi_unregister."); if (tpoll) { debug(2, "avahi: stop the threaded poll."); avahi_threaded_poll_stop(tpoll); @@ -366,6 +364,12 @@ static void avahi_unregister(void) { free(service_name); } else debug(1, "avahi attempt to free NULL service name"); + + if (text_record_string_list) { + debug(2, "avahi free text_record_string_list"); + avahi_string_list_free(text_record_string_list); + } + service_name = NULL; } diff --git a/mdns_dns_sd.c b/mdns_dns_sd.c index cf178f1d..4952a9f0 100644 --- a/mdns_dns_sd.c +++ b/mdns_dns_sd.c @@ -33,7 +33,8 @@ static DNSServiceRef service; -static int mdns_dns_sd_register(char *apname, int port) { +static int mdns_dns_sd_register(char *apname, int port, + __attribute__((unused)) char **txt_records) { char *recordwithoutmetadata[] = {MDNS_RECORD_WITHOUT_METADATA, NULL}; #ifdef CONFIG_METADATA char *recordwithmetadata[] = {MDNS_RECORD_WITH_METADATA, NULL}; diff --git a/mdns_external.c b/mdns_external.c index 974aa85b..17884f94 100644 --- a/mdns_external.c +++ b/mdns_external.c @@ -80,7 +80,8 @@ static int fork_execvp(const char *file, char *const argv[]) { return response; } -static int mdns_external_avahi_register(char *apname, __attribute__((unused)) int port) { +static int mdns_external_avahi_register(char *apname, __attribute__((unused)) int port, + __attribute__((unused)) char **txt_records) { char mdns_port[6]; snprintf(mdns_port, sizeof(mdns_port), "%d", config.port); @@ -119,7 +120,8 @@ static int mdns_external_avahi_register(char *apname, __attribute__((unused)) in return -1; } -static int mdns_external_dns_sd_register(char *apname, __attribute__((unused)) int port) { +static int mdns_external_dns_sd_register(char *apname, __attribute__((unused)) int port, + __attribute__((unused)) char **txt_records) { char mdns_port[6]; snprintf(mdns_port, sizeof(mdns_port), "%d", config.port); diff --git a/mdns_tinysvcmdns.c b/mdns_tinysvcmdns.c index ad6be65a..619f9d31 100644 --- a/mdns_tinysvcmdns.c +++ b/mdns_tinysvcmdns.c @@ -38,7 +38,8 @@ static struct mdnsd *svr = NULL; -static int mdns_tinysvcmdns_register(char *apname, int port) { +static int mdns_tinysvcmdns_register(char *apname, int port, + __attribute__((unused)) char **txt_records) { struct ifaddrs *ifalist; struct ifaddrs *ifa; diff --git a/rtp.c b/rtp.c index 34115c9b..69c04ef2 100644 --- a/rtp.c +++ b/rtp.c @@ -1205,7 +1205,7 @@ int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) { } -#ifdef CONFIG_AIRPLAY_2 +#if 0 void set_ptp_anchor_info(rtsp_conn_info *conn, uint64_t clock_id, uint32_t rtptime, uint64_t networktime) { diff --git a/rtsp.c b/rtsp.c index 2661ccec..8be05db0 100644 --- a/rtsp.c +++ b/rtsp.c @@ -299,7 +299,7 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { #endif -int have_player(rtsp_conn_info *conn) { +int have_play_lock(rtsp_conn_info *conn) { int response = 0; debug_mutex_lock(&playing_conn_lock, 1000000, 3); if (playing_conn == conn) // this connection definitely has the play lock @@ -308,6 +308,102 @@ int have_player(rtsp_conn_info *conn) { return response; } +int try_to_hold_play_lock(rtsp_conn_info *conn) { + int response = -1; + if (pthread_mutex_trylock(&playing_conn_lock) == 0) { + if (playing_conn == conn) { + response = 0; + } else { + pthread_mutex_unlock(&playing_conn_lock); + } + } + return response; +} + +void release_hold_on_play_lock(__attribute__((unused)) rtsp_conn_info *conn) { + pthread_mutex_unlock(&playing_conn_lock); +} + +void release_play_lock(rtsp_conn_info *conn) { + debug_mutex_lock(&playing_conn_lock, 1000000, 3); + if (playing_conn == conn) { // if we have the player + playing_conn = NULL; // let it go + debug(2, "Connection %d: release play lock.", conn->connection_number); + } + debug_mutex_unlock(&playing_conn_lock, 3); +} + +int get_play_lock(rtsp_conn_info *conn) { + // returns -1 if it failed, 0 if it succeeded and 1 if it succeeded but + // interrupted an existing session + int response = 0; + + int have_the_player = 0; + int should_wait = 0; // this will be true if you're trying to break in to the current session + int interrupting_current_session = 0; + + // try to become the current playing_conn + + debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it + + if (playing_conn == NULL) { + playing_conn = conn; + have_the_player = 1; + } else if (playing_conn == conn) { + have_the_player = 1; + warn("Duplicate attempt to acquire the player by the same connection, by the look of it!"); + } else if (playing_conn->stop) { + debug(1, "Connection %d: Waiting for Connection %d to stop playing.", conn->connection_number, + playing_conn->connection_number); + should_wait = 1; +#ifdef CONFIG_AIRPLAY_2 + } else { // ignore the allow_session_interruption in AirPlay 2 -- it is always permissible, it + // seems +#else + } else if (config.allow_session_interruption == 1) { +#endif + debug(1, "Connection %d: Asking Connection %d to stop playing.", conn->connection_number, + playing_conn->connection_number); + playing_conn->stop = 1; + interrupting_current_session = 1; + should_wait = 1; + pthread_cancel(playing_conn->thread); // asking the RTSP thread to exit + } + debug_mutex_unlock(&playing_conn_lock, 3); + + if (should_wait) { + int time_remaining = 3000000; // must be signed, as it could go negative... + + while ((time_remaining > 0) && (have_the_player == 0)) { + debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it + if (playing_conn == NULL) { + playing_conn = conn; + have_the_player = 1; + } + debug_mutex_unlock(&playing_conn_lock, 3); + + if (have_the_player == 0) { + usleep(100000); + time_remaining -= 100000; + } + } + + if ((have_the_player == 1) && (interrupting_current_session == 1)) { + debug(1, "Connection %d: got player lock", conn->connection_number); + response = 1; + } else { + debug(1, "Connection %d: failed to get player lock", conn->connection_number); + response = -1; + } + } + + if ((have_the_player) && (interrupting_current_session == 0)) { + debug(2, "Connection %d: got player lock.", conn->connection_number); + response = 0; + } + return response; +} + void player_watchdog_thread_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; debug(3, "Connection %d: Watchdog Exit.", conn->connection_number); @@ -853,7 +949,7 @@ int msg_write_response(int fd, rtsp_message *resp) { void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: RECORD", conn->connection_number); - if (have_player(conn)) { + if (have_play_lock(conn)) { if (conn->player_thread) warn("Connection %d: RECORD: Duplicate RECORD message -- ignored", conn->connection_number); else @@ -909,7 +1005,7 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message * void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: TEARDOWN", conn->connection_number); - if (have_player(conn)) { + if (have_play_lock(conn)) { resp->respcode = 200; msg_add_header(resp, "Connection", "close"); debug( @@ -928,7 +1024,7 @@ void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: FLUSH", conn->connection_number); - if (have_player(conn)) { + if (have_play_lock(conn)) { char *p = NULL; uint32_t rtptime = 0; char *hdr = msg_get_header(req, "RTP-Info"); @@ -968,7 +1064,7 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: SETUP", conn->connection_number); resp->respcode = 451; // invalid arguments -- expect them - if (have_player(conn)) { + if (have_play_lock(conn)) { uint16_t cport, tport; char *ar = msg_get_header(req, "Active-Remote"); if (ar) { @@ -1962,66 +2058,14 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: ANNOUNCE", conn->connection_number); - - int have_the_player = 0; - int should_wait = 0; // this will be true if you're trying to break in to the current session - int interrupting_current_session = 0; - - // try to become the current playing_conn - - debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it - - if (playing_conn == NULL) { - playing_conn = conn; - have_the_player = 1; - } else if (playing_conn == conn) { - have_the_player = 1; - warn("Duplicate ANNOUNCE, by the look of it!"); - } else if (playing_conn->stop) { - debug(1, "Connection %d ANNOUNCE is waiting for connection %d to shut down.", - conn->connection_number, playing_conn->connection_number); - should_wait = 1; - } else if (config.allow_session_interruption == 1) { - debug(2, "Connection %d: ANNOUNCE: asking playing connection %d to shut down.", - conn->connection_number, playing_conn->connection_number); - playing_conn->stop = 1; - interrupting_current_session = 1; - should_wait = 1; - pthread_cancel(playing_conn->thread); // asking the RTSP thread to exit - } - debug_mutex_unlock(&playing_conn_lock, 3); - - if (should_wait) { - int time_remaining = 3000000; // must be signed, as it could go negative... - - while ((time_remaining > 0) && (have_the_player == 0)) { - debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it - if (playing_conn == NULL) { - playing_conn = conn; - have_the_player = 1; - } - debug_mutex_unlock(&playing_conn_lock, 3); - - if (have_the_player == 0) { - usleep(100000); - time_remaining -= 100000; - } - } - - if ((have_the_player == 1) && (interrupting_current_session == 1)) { - debug(2, "Connection %d: ANNOUNCE got the player", conn->connection_number); - } else { - debug(2, "Connection %d: ANNOUNCE failed to get the player", conn->connection_number); - } - } - - if (have_the_player) { + int get_play_status = get_play_lock(conn); + if (get_play_status != -1) { debug(3, "Connection %d: ANNOUNCE has acquired play lock.", conn->connection_number); // now, if this new session did not break in, then it's okay to reset the next UDP ports // to the start of the range - if (interrupting_current_session == 0) { // will be zero if it wasn't waiting to break in + if (get_play_status == 1) { // will be zero if it wasn't waiting to break in resetFreeUDPPort(); } @@ -2036,7 +2080,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag cp_left -= strlen(cp) + 1; } } -*/ + */ conn->stream.type = ast_unknown; resp->respcode = 456; // 456 - Header Field Not Valid for Resource @@ -2815,7 +2859,7 @@ void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) { pthread_setcancelstate(oldState, NULL); } -void rtsp_listen_loop(void) { +void *rtsp_listen_loop(__attribute((unused)) void *arg) { int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); struct addrinfo hints, *info, *p; @@ -2916,7 +2960,82 @@ void rtsp_listen_loop(void) { maxfd = sockfd[i]; } - mdns_register(); + // make up a txt record + char *txt_records[64]; + char **p = txt_records; + +#ifdef CONFIG_AIRPLAY_2 + *p++ = "srcvers=366.0"; + char deviceIdString[64]; + snprintf(deviceIdString, sizeof(deviceIdString) - 1, "deviceid=%s", config.airplay_device_id); + *p++ = deviceIdString; + // features is a 64 bit number, least significant 32 bits + + char featuresString[64]; + uint64_t features_hi = config.airplay_features; + features_hi = (features_hi >> 32) & 0xffffffff; + uint64_t features_lo = config.airplay_features; + features_lo = features_lo & 0xffffffff; + snprintf(featuresString, sizeof(featuresString) - 1, "features=0x%" PRIx64 ",0x%" PRIx64 "", + features_lo, features_hi); + + *p++ = featuresString; + *p++ = "flags=0x4"; + *p++ = "protovers=1.1"; + *p++ = "acl=0"; + *p++ = "rsf=0x0"; + *p++ = "fv=p20.78000.12"; + *p++ = "model=SPS"; + char piString[64]; + snprintf(piString, sizeof(piString) - 1, "pi=%s", config.airplay_pi); + *p++ = piString; + char gidString[64]; + snprintf(gidString, sizeof(gidString) - 1, "gid=%s", config.airplay_gid); + *p++ = gidString; + *p++ = "gcgl=0"; + char pkString[128]; + snprintf(pkString, sizeof(pkString) - 1, "pk=%s", config.airplay_pk); + *p++ = pkString; + *p++ = NULL; +#else + // here, just replicate what happens in mdns.h when using those #defines + *p++ = "sf=0x4"; + *p++ = "fv=76400.10"; + *p++ = "am=ShairportSync"; + *p++ = "vs=105.1"; + *p++ = "tp=TCP,UDP"; + *p++ = "vn=65537"; +#ifdef CONFIG_METADATA + if (config.get_coverart == 0) + *p++ = "md=0,2"; + else + *p++ = "md=0,1,2"; +#endif + *p++ = "ss=16"; + *p++ = "sr=44100"; + *p++ = "da=true"; + *p++ = "sv=false"; + *p++ = "et=0,1"; + *p++ = "ek=1"; + *p++ = "cn=0,1"; + *p++ = "ch=2"; + *p++ = "am=ShairportSync"; + *p++ = "txtvers=1"; + if (config.password == 0) + *p++ = "pw=false"; + else + *p++ = "pw=true"; + *p++ = NULL; +#endif + + /* + debug(1,"txt_record:"); + p = txt_records; + while (*p) + debug(1," %s", *p++); + */ + + mdns_register(txt_records); pthread_setcancelstate(oldState, NULL); int acceptfd; @@ -3030,4 +3149,6 @@ void rtsp_listen_loop(void) { config.port); } // debug(1, "Oops -- fell out of the RTSP select loop"); + debug(1, "Oops -- fell out of the RTSP select loop"); + pthread_exit(NULL); } diff --git a/rtsp.h b/rtsp.h index 703c1006..89f5e151 100644 --- a/rtsp.h +++ b/rtsp.h @@ -6,7 +6,8 @@ extern rtsp_conn_info *playing_conn; extern rtsp_conn_info **conns; -void rtsp_listen_loop(void); +void *rtsp_listen_loop(__attribute((unused)) void *arg); + // void rtsp_shutdown_stream(void); void rtsp_request_shutdown_stream(void); diff --git a/shairport.c b/shairport.c index a32216d3..6168b59e 100644 --- a/shairport.c +++ b/shairport.c @@ -2,7 +2,7 @@ * Shairport, an Apple Airplay receiver * Copyright (c) James Laird 2013 * All rights reserved. - * Modifications and additions (c) Mike Brady 2014--2019 + * Modifications and additions (c) Mike Brady 2014--2021 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -43,6 +43,11 @@ #include "config.h" +#ifdef CONFIG_AIRPLAY_2 +#include +#include +#endif + #ifdef CONFIG_MBEDTLS #include #include @@ -110,6 +115,8 @@ pid_t pid; int this_is_the_daemon_process = 0; #endif +pthread_t rtsp_listener_thread; + int killOption = 0; int daemonisewith = 0; int daemonisewithout = 0; @@ -494,8 +501,13 @@ int parse_options(int argc, char **argv) { /* Get the port setting. */ if (config_lookup_int(config.cfg, "general.port", &value)) { if ((value < 0) || (value > 65535)) +#ifdef CONFIG_AIRPLAY_2 + die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 7000", + value); +#else die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 5000", value); +#endif else config.port = value; } @@ -1234,7 +1246,11 @@ int parse_options(int argc, char **argv) { /* if the regtype hasn't been set, do it now */ if (config.regtype == NULL) +#ifdef CONFIG_AIRPLAY_2 + config.regtype = strdup("_airplay._tcp"); +#else config.regtype = strdup("_raop._tcp"); +#endif if (tdebuglev != 0) debuglev = tdebuglev; @@ -1313,6 +1329,11 @@ const char *pid_file_proc(void) { } #endif +void exit_rtsp_listener() { + pthread_cancel(rtsp_listener_thread); + pthread_join(rtsp_listener_thread, NULL); // not sure you need this +} + void exit_function() { if (emergency_exit == 0) { @@ -1342,13 +1363,14 @@ void exit_function() { #endif */ #ifdef CONFIG_DBUS_INTERFACE - stop_dbus_service(); + debug(2, "Stopping D-Bus service"); + stop_dbus_service(); #endif - if (g_main_loop) { - debug(2, "Stopping DBUS Loop Thread"); - g_main_loop_quit(g_main_loop); - pthread_join(dbus_thread, NULL); - } + if (g_main_loop) { + debug(2, "Stopping D-Bus Loop Thread"); + g_main_loop_quit(g_main_loop); + pthread_join(dbus_thread, NULL); + } #endif #ifdef CONFIG_DACP_CLIENT @@ -1362,8 +1384,11 @@ void exit_function() { #endif #ifdef CONFIG_METADATA - metadata_stop(); // close down the metadata pipe + debug(2, "Stopping metadata"); + metadata_stop(); // close down the metadata pipe #endif + debug(2, "Deinitialising the audio backend."); + activity_monitor_stop(0); activity_monitor_stop(0); @@ -1373,8 +1398,9 @@ void exit_function() { } #ifdef CONFIG_SOXR - // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down - pthread_join(soxr_time_check_thread, NULL); + // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down + debug(2, "Waiting for SoXr timecheck to terminate..."); + pthread_join(soxr_time_check_thread, NULL); #endif if (conns) @@ -1436,8 +1462,14 @@ void handle_sigchld(__attribute__((unused)) int sig) { errno = saved_errno; } -void main_thread_cleanup_handler(__attribute__((unused)) void *arg) { - debug(2, "main thread cleanup handler called"); +// for clean exits +void intHandler(__attribute__((unused)) int k) { + debug(2, "exit on SIGINT"); + exit(EXIT_SUCCESS); +} + +void termHandler(__attribute__((unused)) int k) { + debug(2, "exit on SIGTERM"); exit(EXIT_SUCCESS); } @@ -1535,7 +1567,54 @@ int main(int argc, char **argv) { config.tolerance = 0.002; // this number of seconds of timing error before attempting to correct it. config.buffer_start_fill = 220; + +#if 0 +// #ifdef CONFIG_AIRPLAY_2 + config.timeout = 0; // disable watchdog + config.port = 7000; + // the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32 + // bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as + // features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist: + // 496155702020608 this setting here is the source of both the plist features response and the + // mDNS string. + config.airplay_features = 0x1C340405F4A00; + // get a device id -- the first non-local MAC address, not necessarily the one in use + config.airplay_device_id = get_device_id(); + if (config.airplay_device_id) { + debug(1, "Started in Airplay 2 mode on device \"%s\"!", config.airplay_device_id); + } else + debug(1, "Started in Airplay 2 mode!"); + + // now make up a 32-byte "pk" string from random numbers + const ssize_t pk_size = 32; + char pk_bytes[pk_size]; + char pk_string[pk_size * 2 + 1]; + randombytes_buf(pk_bytes, pk_size); // using libsodium + char *obfp = pk_string; + int obfc; + for (obfc = 0; obfc < pk_size; obfc++) { + snprintf(obfp, 3, "%02X", pk_bytes[obfc]); + obfp += 2; + }; + *obfp = 0; + debug(1, "pk string: \"%s\"", pk_string); + config.airplay_pk = strdup(pk_string); + + // now generate a UUID + // from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c + // with thanks + uuid_t binuuid; + uuid_generate_random(binuuid); + char *uuid = malloc(UUID_STR_LEN); + /* Produces a UUID string at uuid consisting of lower-case letters. */ + uuid_unparse_lower(binuuid, uuid); + config.airplay_pi = strdup(uuid); + config.airplay_gid = strdup(uuid); // initially the gid is the same as the pi + + // now we need to create the sequence of items for a Bonjour TXT record. +#else config.port = 5000; +#endif #ifdef CONFIG_SOXR config.packet_stuffing = ST_auto; // use soxr interpolation by default if support has been @@ -1728,6 +1807,24 @@ int main(int argc, char **argv) { #endif debug(1, "Started!"); +#ifdef CONFIG_AIRPLAY_2 + debug(1, "Started in Airplay 2 mode!"); +#else + debug(1, "Started in Airplay 1 mode!"); +#endif + + // control-c (SIGINT) cleanly + struct sigaction act; + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = intHandler; + sigaction(SIGINT, &act, NULL); + + // terminate (SIGTERM) + struct sigaction act2; + memset(&act2, 0, sizeof(struct sigaction)); + act2.sa_handler = termHandler; + sigaction(SIGTERM, &act2, NULL); + // stop a pipe signal from killing the program signal(SIGPIPE, SIG_IGN); @@ -1768,7 +1865,7 @@ int main(int argc, char **argv) { } config.output->init(argc - audio_arg, argv + audio_arg); - pthread_cleanup_push(main_thread_cleanup_handler, NULL); + // pthread_cleanup_push(main_cleanup_handler, NULL); // daemon_log(LOG_NOTICE, "startup"); @@ -1784,6 +1881,14 @@ int main(int argc, char **argv) { break; } +#ifdef CONFIG_AIRPLAY_2 + if (sodium_init() < 0) { + debug(1, "Can't initialise libsodium!"); + } else { + debug(1, "libsodium initialised."); + } +#endif + /* Mess around with the latency options */ // Basically, we expect the source to set the latency and add a fixed offset of 11025 frames to // it, which sounds right @@ -1983,8 +2088,9 @@ int main(int argc, char **argv) { } #endif - activity_monitor_start(); - rtsp_listen_loop(); - pthread_cleanup_pop(1); + activity_monitor_start(); // not yet for AP2 + pthread_create(&rtsp_listener_thread, NULL, &rtsp_listen_loop, NULL); + atexit(exit_rtsp_listener); + pthread_join(rtsp_listener_thread, NULL); return 0; }