Lot of changes here.

1. check_classic_mac_basic.yml — update to latest macOS-latest
2. docker-on-push-tag-or-pr — dependant updates
3. FFTConvolver — fix a compilation warning when the DEBUG flag is turned off
4. Reorganise some source files — e.g. move most of the metadata stuff into a subdirectory
5. Remove unused special-purpose AirPlay 2 RC event handler.
6. Reorganise source files and source code relating to the standard event handler.
7. Check configuration settings a little more carefully to offer more meaningful diagnostics.
8. Add password to AirPlay 2 and reorganise initialisation to set AirPlay 2 flags accordingly.  Also, remove redundant airplay_pin.
9. Remove redundant debug facility definitions from common.h
10. Make it possible to omit all the dacp stuff from a build.
11. Make it possible to omit metadata multicast from a build.
12. Add AP2-native metadata by setting bit 50 of features to true.
13. Improve stability when pre-empting an existing play session.
14. Fix a few compiler warnings in the pair_ap library.
15. Ensure that the audio format is known before starting synchronisation.
16. Improve handle_configure to set/clear a new config setting. Doesn’t seem to do anything…
17. Experimentally turn off bit 46 of AirPlay 2 features — seems to stop failing pair-verify attempts.
This commit is contained in:
Mike Brady
2026-04-26 10:23:18 +01:00
parent e2bb272d93
commit 11052b7896
56 changed files with 2672 additions and 1947 deletions
+95 -26
View File
@@ -23,6 +23,7 @@ SOFTWARE.
*/
#include "debug.h"
#include <ctype.h> // for isprint()
#include <inttypes.h>
#include <pthread.h>
#include <stdarg.h>
@@ -64,13 +65,9 @@ void debug_init(int level, int show_elapsed_time, int show_relative_time, int sh
debugger_show_file_and_line = show_file_and_line;
}
int debug_level() {
return debuglev;
};
int debug_level() { return debuglev; };
void set_debug_level(int level) {
debuglev = level;
}
void set_debug_level(int level) { debuglev = level; }
void increase_debug_level() {
if (debuglev < 3)
@@ -82,28 +79,15 @@ void decrease_debug_level() {
debuglev--;
}
int get_show_elapsed_time() {
return debugger_show_elapsed_time;
}
void set_show_elapsed_time(int setting) {
debugger_show_elapsed_time = setting;
}
int get_show_relative_timel() {
return debugger_show_relative_time;
}
int get_show_elapsed_time() { return debugger_show_elapsed_time; }
void set_show_elapsed_time(int setting) { debugger_show_elapsed_time = setting; }
int get_show_relative_timel() { return debugger_show_relative_time; }
void set_show_relative_time(int setting) {
debugger_show_relative_time = setting;
}
void set_show_relative_time(int setting) { debugger_show_relative_time = setting; }
int get_show_file_and_line() {
return debugger_show_file_and_line;
}
int get_show_file_and_line() { return debugger_show_file_and_line; }
void set_show_file_and_line(int setting) {
debugger_show_file_and_line = setting;
}
void set_show_file_and_line(int setting) { debugger_show_file_and_line = setting; }
char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss, double tsl,
const char *filename, const int linenumber, const char *prefix) {
@@ -244,10 +228,94 @@ void _inform(const char *filename, const int linenumber, const char *format, ...
pthread_setcancelstate(oldState, NULL);
}
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *ibuf,
size_t buf_len) {
if (level > debuglev)
return;
char *vbuf = (char *)ibuf; // this to stop the compiler complaining about indexing through a void
unsigned int i;
unsigned int hexdump_cols = 16;
// 0x123456: <hexdump_cols * 3> <hexdump_cols>\n
const size_t buffer_size = 2 + 6 + 2 + hexdump_cols * 3 + hexdump_cols + 1;
char *buf = malloc(buffer_size);
if (buf) {
//char *bufp = buf;
// *buf = '\0';
// if (msg)
// printf("%s", msg);
#define ADDR_PREFIX_LEN 10 // "0x000000: "
// each complete row
for (i = 0; i < (buf_len / hexdump_cols); i++) {
char *bufp = buf;
*buf = '\0';
snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06x ", (i * hexdump_cols) & 0xFFFFFF);
bufp += ADDR_PREFIX_LEN;
unsigned int j;
for (j = 0; j < hexdump_cols; j++) {
snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[i * hexdump_cols + j]);
bufp += strlen("12 ");
}
for (j = 0; j < hexdump_cols; j++) {
if (isprint(vbuf[i * hexdump_cols + j])) {
*bufp = 0xFF & vbuf[i * hexdump_cols + j];
} else {
*bufp = '.';
}
bufp++;
}
*bufp = '\0';
_debug(thefilename, linenumber, level, "%s", buf);
}
size_t remaining = buf_len % hexdump_cols;
if (remaining != 0) {
size_t starting_offset = buf_len - remaining;
char *bufp = buf;
*buf = '\0';
snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06zx ", starting_offset & 0xFFFFFF);
bufp += ADDR_PREFIX_LEN;
unsigned int j;
for (j = 0; j < hexdump_cols; j++) {
if (j < remaining) {
snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[starting_offset + j]);
} else {
snprintf(bufp, strlen(" ") + 1, " "); // fille with blanks
}
bufp += strlen("12 ");
}
for (j = 0; j < remaining; j++) {
if (isprint(vbuf[i * hexdump_cols + j])) {
*bufp = 0xFF & vbuf[starting_offset + j];
} else {
*bufp = '.';
}
bufp++;
}
*bufp = '\0';
_debug(thefilename, linenumber, level, "%s", buf);
}
free(buf);
}
}
/*
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *vbuf,
size_t buf_len) {
if (level > debuglev)
return;
new_debug_print_buffer(thefilename, linenumber, level, vbuf, buf_len);
char *buf = (char *)vbuf;
char *obf =
malloc(buf_len * 4 + 1); // to be on the safe side -- 4 characters on average for each byte
@@ -274,4 +342,5 @@ void _debug_print_buffer(const char *thefilename, const int linenumber, int leve
_debug(thefilename, linenumber, level, "%s", obf);
free(obf);
}
}
}
*/
+48
View File
@@ -0,0 +1,48 @@
/*
* Generate a device-related UUID. This file is part of Shairport Sync
* Copyright (c) Mike Brady 2026
* Modifications, including those associated with audio synchronization, multithreading and
* metadata handling copyright (c) Mike Brady 2014--2025
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include "generate_device_uuid.h"
#include "definitions.h"
// user is responsible for deallocating returned string
char *generate_device_uuid(const char *device_id) {
uuid_t namespace_uuid;
uuid_t derived_uuid;
uuid_parse(SHAIRPORT_SYNC_DEVICE_NAMESPACE, namespace_uuid);
uuid_generate_sha1(derived_uuid, namespace_uuid, device_id, strlen(device_id));
char *uuid = malloc(UUID_STR_LEN + 1);
uuid_unparse_lower(derived_uuid, uuid);
return uuid;
}
+3
View File
@@ -0,0 +1,3 @@
#pragma once
char *generate_device_uuid(const char *device_id);
+47
View File
@@ -0,0 +1,47 @@
/*
* Generate a random UUID. This file is part of Shairport Sync
* Copyright (c) Mike Brady 2026
* Modifications, including those associated with audio synchronization, multithreading and
* metadata handling copyright (c) Mike Brady 2014--2025
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <uuid/uuid.h>
#include "generate_random_uuid.h"
// user is responsible for deallocating returned string
char *generate_random_uuid() {
// 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 + 1); // leave space for the NUL at the end
// Produces a UUID string at uuid consisting of lower-case letters
uuid_unparse_lower(binuuid, uuid);
return uuid;
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef _GENERATE_RANDOM_UUID_H
#define _GENERATE_RANDOM_UUID_H
char *generate_random_uuid();
#endif // _GENERATE_RANDOM_UUID_H
+2 -2
View File
@@ -38,8 +38,8 @@ int eintr_checked_accept(int sockfd, struct sockaddr *addr,
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1,
"error %d accept()ing a socketin ap2_event_receiver: \"%s\". (Note: error %d will be ignored.)",
errno, errorstring, EINTR);
"error %d accept()ing a socket %d: \"%s\". (Note: error %d will be ignored.)",
errno, sockfd, errorstring, EINTR);
}
} while((response == -1) && (errno == EINTR));
+265
View File
@@ -0,0 +1,265 @@
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <plist/plist.h>
#include "rtsp_message_utilities.h"
#include "../rtsp.h"
#include "../common.h"
#ifdef HAVE_LIBPLIST_GE_2_3_0
#define plist_from_memory(plist_data, length, plist) \
plist_from_memory((plist_data), (length), (plist), NULL)
#endif
// every time we want to retain or release a reference count, lock it with this
// if a reference count is read as zero, it means the it's being deallocated.
static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER;
static int msg_indexes = 1;
void msg_retain(rtsp_message *msg) {
int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4);
if (rc)
debug(1, "Error %d locking reference counter lock", rc);
if (msg > (rtsp_message *)0x00010000) {
msg->referenceCount++;
debug(4, "msg_free increment reference counter message %d to %d.", msg->index_number,
msg->referenceCount);
// debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount);
rc = pthread_mutex_unlock(&reference_counter_lock);
if (rc)
debug(1, "Error %d unlocking reference counter lock", rc);
} else {
debug(1, "invalid rtsp_message pointer 0x%" PRIxPTR " passed to retain", (uintptr_t)msg);
}
}
rtsp_message *msg_init(void) {
// no thread cancellation points here
int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4);
if (rc)
debug(1, "Error %d locking reference counter lock", rc);
rtsp_message *msg = malloc(sizeof(rtsp_message));
if (msg) {
memset(msg, 0, sizeof(rtsp_message));
msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
msg->index_number = msg_indexes++;
debug(4, "msg_init message %d", msg->index_number);
} else {
die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes);
}
// debug(1,"msg_init -- create item %d.", msg->index_number);
rc = pthread_mutex_unlock(&reference_counter_lock);
if (rc)
debug(1, "Error %d unlocking reference counter lock", rc);
return msg;
}
int msg_add_header(rtsp_message *msg, char *name, char *value) {
if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) {
warn("too many headers?!");
return 1;
}
msg->name[msg->nheaders] = strdup(name);
msg->value[msg->nheaders] = strdup(value);
msg->nheaders++;
return 0;
}
char *msg_get_header(rtsp_message *msg, char *name) {
unsigned int i;
for (i = 0; i < msg->nheaders; i++)
if (!strcasecmp(msg->name[i], name))
return msg->value[i];
return NULL;
}
void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level,
rtsp_message *msg) {
unsigned int i;
if (conn != NULL) {
if (msg->respcode != 0)
_debug(filename, linenumber, level, "Connection: %d, Response Code: %d.", conn->connection_number, msg->respcode);
for (i = 0; i < msg->nheaders; i++) {
_debug(filename, linenumber, level, "Connection: %d, Type: \"%s\", content: \"%s\"", conn->connection_number, msg->name[i],
msg->value[i]);
}
} else {
if (msg->respcode != 0)
_debug(filename, linenumber, level, " Response Code: %d.", msg->respcode);
for (i = 0; i < msg->nheaders; i++) {
_debug(filename, linenumber, level, " Type: \"%s\", content: \"%s\"", msg->name[i],
msg->value[i]);
}
}
}
void msg_free(rtsp_message **msgh) {
debug_mutex_lock(&reference_counter_lock, 1000, 0);
if (*msgh > (rtsp_message *)0x00010000) {
rtsp_message *msg = *msgh;
msg->referenceCount--;
if (msg->referenceCount)
debug(4, "msg_free decrement reference counter message %d to %d", msg->index_number,
msg->referenceCount);
if (msg->referenceCount == 0) {
unsigned int i;
for (i = 0; i < msg->nheaders; i++) {
free(msg->name[i]);
free(msg->value[i]);
}
if (msg->content)
free(msg->content);
// debug(1,"msg_free item %d -- free.",msg->index_number);
uintptr_t index = (msg->index_number) & 0xFFFF;
if (index == 0)
index = 0x10000; // ensure it doesn't fold to zero.
*msgh =
(rtsp_message *)(index); // put a version of the index number of the freed message in here
debug(4, "msg_free freed message %d", msg->index_number);
free(msg);
} else {
// debug(1,"msg_free item %d -- decrement reference to
// %d.",msg->index_number,msg->referenceCount);
}
} else if (*msgh != NULL) {
debug(1,
"msg_free: error attempting to free an allocated but already-freed rtsp_message, number "
"%" PRIxPTR ".",
(uintptr_t)*msgh);
}
debug_mutex_unlock(&reference_counter_lock, 0);
}
int msg_handle_line(rtsp_message **pmsg, char *line) {
rtsp_message *msg = *pmsg;
if (!msg) {
msg = msg_init();
*pmsg = msg;
char *sp, *p;
sp = NULL; // this is to quieten a compiler warning
debug(4, "RTSP/HTTP Message Received: \"%s\".", line);
p = strtok_r(line, " ", &sp);
if (!p)
goto fail;
strncpy(msg->method, p, sizeof(msg->method) - 1);
p = strtok_r(NULL, " ", &sp);
if (!p)
goto fail;
strncpy(msg->path, p, sizeof(msg->path) - 1);
p = strtok_r(NULL, " ", &sp);
if (!p)
goto fail;
if ((strcmp(p, "RTSP/1.0") != 0) && (strcmp(p, "HTTP/1.1") != 0)) {
debug(1, "Problem with Message: \"%s\"", p);
goto fail;
}
return -1;
}
if (strlen(line)) {
char *p;
p = strstr(line, ": ");
if (!p) {
warn("bad header: >>%s<<", line);
goto fail;
}
*p = 0;
p += 2;
msg_add_header(msg, line, p);
debug(4, " %s: %s.", line, p);
return -1;
} else {
char *cl = msg_get_header(msg, "Content-Length");
if (cl)
return atoi(cl);
else
return 0;
}
fail:
debug(1, "msg_handle_line fail");
msg_free(pmsg);
*pmsg = NULL;
return 0;
}
#ifdef CONFIG_AIRPLAY_2
int rtsp_message_contains_plist(rtsp_message *message) {
int reply = 0; // assume there is no plist in the message
if ((message->contentlength >= strlen("bplist00")) &&
(strstr(message->content, "bplist00") == message->content))
reply = 1;
return reply;
}
plist_t plist_from_rtsp_content(rtsp_message *message) {
plist_t the_plist = NULL;
if (rtsp_message_contains_plist(message)) {
plist_from_memory(message->content, message->contentlength, &the_plist);
}
return the_plist;
}
char *plist_as_xml_text(plist_t the_plist) {
// caller must free the returned character buffer
// convert it to xml format
uint32_t size;
char *plist_out = NULL;
plist_to_xml(the_plist, &plist_out, &size);
// put it into a NUL-terminated string
char *reply = malloc(size + 1);
if (reply) {
memcpy(reply, plist_out, size);
reply[size] = '\0';
}
if (plist_out)
free(plist_out);
return reply;
}
// caller must free the returned character buffer
char *rtsp_plist_content(rtsp_message *message) {
char *reply = NULL;
// first, check if it has binary plist content
if (rtsp_message_contains_plist(message))
reply = plist_as_xml_text(plist_from_rtsp_content(message));
return reply;
}
#endif
void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt,
rtsp_message *message) {
if (level > debug_level())
return;
if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list...
_debug(filename, linenumber, level, "%s", prompt);
_debug_print_msg_headers(conn, filename, linenumber, level, message);
#ifdef CONFIG_AIRPLAY_2
char *plist_content = rtsp_plist_content(message);
if (plist_content) {
_debug(filename, linenumber, level, " Content length: %u. Content Plist (as XML):\n--\n%s--", message->contentlength, plist_content);
free(plist_content);
} else
#endif
{
_debug(filename, linenumber, level, " No Content Plist. Content length: %u.",
message->contentlength);
if (message->contentlength > 0) {
_debug_print_buffer(filename, linenumber, level, message->content, message->contentlength);
}
}
}
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include "player.h"
#include "rtsp.h"
rtsp_message *msg_init(void);
int msg_handle_line(rtsp_message **pmsg, char *line);
int msg_add_header(rtsp_message *msg, char *name, char *value);
char *msg_get_header(rtsp_message *msg, char *name);
void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt,
rtsp_message *message);
#define debug_log_rtsp_message_conn(conn, level, prompt, message) \
_debug_log_rtsp_message(conn, __FILE__, __LINE__, level, prompt, message)
#define debug_log_rtsp_message(level, prompt, message) \
_debug_log_rtsp_message(NULL, __FILE__, __LINE__, level, prompt, message)
void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level,
rtsp_message *msg);
#define debug_print_msg_headers(level, message) \
_debug_print_msg_headers(NULL, __FILE__, __LINE__, level, message)
#define debug_print_msg_headers_conn(level, message) \
_debug_print_msg_headers(conn, __FILE__, __LINE__, level, message)
#ifdef CONFIG_AIRPLAY_2
int rtsp_message_contains_plist(rtsp_message *message);
plist_t plist_from_rtsp_content(rtsp_message *message);
#endif