From 743f92d11403d7e29800c6feadfa3fa831a085f4 Mon Sep 17 00:00:00 2001 From: fduncanh Date: Tue, 10 Aug 2021 01:19:39 -0400 Subject: [PATCH] should have been part of previous commit --- lib/raop.c | 10 +- lib/raop.h | 484 ++++++++++++++++++++++++++++++++++++++------ lib/raop_handlers.h | 20 +- 3 files changed, 442 insertions(+), 72 deletions(-) diff --git a/lib/raop.c b/lib/raop.c index df1302e..cd7ca82 100755 --- a/lib/raop.c +++ b/lib/raop.c @@ -363,10 +363,16 @@ void raop_set_display(raop_t *raop, unsigned short width, unsigned short height, if (width) raop->display_width = width; if (height) raop->display_height = height; - if (refresh_rate) raop->display_refresh_rate = refresh_rate; + + // these must fit into a single byte + if (refresh_rate > 255) refresh_rate = 255; + if (max_fps > 255) max_fps = 255; + + if (refresh_rate) raop->display_refresh_rate = refresh_rate; if (max_fps) raop->display_max_fps = max_fps; + if (raop->display_max_fps > raop->display_refresh_rate) { - raop->display_max_fps = raop->display_refresh_rate; + raop->display_max_fps = raop->display_refresh_rate; } } diff --git a/lib/raop.h b/lib/raop.h index bd02d17..cd7ca82 100755 --- a/lib/raop.h +++ b/lib/raop.h @@ -1,73 +1,437 @@ -#ifndef RAOP_H -#define RAOP_H +/** + * Copyright (C) 2011-2012 Juho Vähä-Herttua + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ -#include "dnssd.h" -#include "stream.h" +#include +#include +#include +#include + +#include "raop.h" +#include "raop_rtp.h" +#include "raop_rtp.h" +#include "pairing.h" +#include "httpd.h" + +#include "global.h" +#include "fairplay.h" +#include "netutils.h" +#include "logger.h" +#include "compat.h" +#include "raop_rtp_mirror.h" #include "raop_ntp.h" -#if defined (WIN32) && defined(DLL_EXPORT) -# define RAOP_API __declspec(dllexport) -#else -# define RAOP_API -#endif +struct raop_s { + /* Callbacks for audio and video */ + raop_callbacks_t callbacks; -#ifdef __cplusplus -extern "C" { -#endif + /* Logger instance */ + logger_t *logger; + /* Pairing, HTTP daemon and RSA key */ + pairing_t *pairing; + httpd_t *httpd; -/* Define syslog style log levels */ -#define RAOP_LOG_EMERG 0 /* system is unusable */ -#define RAOP_LOG_ALERT 1 /* action must be taken immediately */ -#define RAOP_LOG_CRIT 2 /* critical conditions */ -#define RAOP_LOG_ERR 3 /* error conditions */ -#define RAOP_LOG_WARNING 4 /* warning conditions */ -#define RAOP_LOG_NOTICE 5 /* normal but significant condition */ -#define RAOP_LOG_INFO 6 /* informational */ -#define RAOP_LOG_DEBUG 7 /* debug-level messages */ + dnssd_t *dnssd; + unsigned short port; + unsigned short timing_lport; + unsigned short control_lport; + unsigned short data_lport; + unsigned short mirror_data_lport; -typedef struct raop_s raop_t; - -typedef void (*raop_log_callback_t)(void *cls, int level, const char *msg); - -struct raop_callbacks_s { - void* cls; - - void (*audio_process)(void *cls, raop_ntp_t *ntp, aac_decode_struct *data); - void (*video_process)(void *cls, raop_ntp_t *ntp, h264_decode_struct *data); - - /* Optional but recommended callback functions */ - void (*conn_init)(void *cls); - void (*conn_destroy)(void *cls); - void (*audio_flush)(void *cls); - void (*video_flush)(void *cls); - void (*audio_set_volume)(void *cls, float volume); - void (*audio_set_metadata)(void *cls, const void *buffer, int buflen); - void (*audio_set_coverart)(void *cls, const void *buffer, int buflen); - void (*audio_remote_control_id)(void *cls, const char *dacp_id, const char *active_remote_header); - void (*audio_set_progress)(void *cls, unsigned int start, unsigned int curr, unsigned int end); + unsigned short display_width; + unsigned short display_height; + unsigned short display_refresh_rate; + unsigned short display_max_fps; }; -typedef struct raop_callbacks_s raop_callbacks_t; -RAOP_API raop_t *raop_init(int max_clients, raop_callbacks_t *callbacks); +struct raop_conn_s { + raop_t *raop; + raop_ntp_t *raop_ntp; + raop_rtp_t *raop_rtp; + raop_rtp_mirror_t *raop_rtp_mirror; + fairplay_t *fairplay; + pairing_session_t *pairing; -RAOP_API void raop_set_log_level(raop_t *raop, int level); -RAOP_API void raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls); -RAOP_API void raop_set_display(raop_t *raop, unsigned short width, unsigned short height, - unsigned short refresh_rate, unsigned short max_fps); -RAOP_API void raop_set_port(raop_t *raop, unsigned short port); -RAOP_API void raop_set_udp_ports(raop_t *raop, unsigned short port[3]); -RAOP_API void raop_set_tcp_ports(raop_t *raop, unsigned short port[2]); -RAOP_API unsigned short raop_get_port(raop_t *raop); -RAOP_API void *raop_get_callback_cls(raop_t *raop); -RAOP_API int raop_start(raop_t *raop, unsigned short *port); -RAOP_API int raop_is_running(raop_t *raop); -RAOP_API void raop_stop(raop_t *raop); -RAOP_API void raop_set_dnssd(raop_t *raop, dnssd_t *dnssd); -RAOP_API void raop_destroy(raop_t *raop); + unsigned char *local; + int locallen; -#ifdef __cplusplus + unsigned char *remote; + int remotelen; + +}; +typedef struct raop_conn_s raop_conn_t; + +#include "raop_handlers.h" + +static void * +conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen) { + raop_t *raop = opaque; + raop_conn_t *conn; + + assert(raop); + + conn = calloc(1, sizeof(raop_conn_t)); + if (!conn) { + return NULL; + } + conn->raop = raop; + conn->raop_rtp = NULL; + conn->raop_ntp = NULL; + conn->fairplay = fairplay_init(raop->logger); + + if (!conn->fairplay) { + free(conn); + return NULL; + } + conn->pairing = pairing_session_init(raop->pairing); + if (!conn->pairing) { + fairplay_destroy(conn->fairplay); + free(conn); + return NULL; + } + + if (locallen == 4) { + logger_log(conn->raop->logger, LOGGER_INFO, + "Local: %d.%d.%d.%d", + local[0], local[1], local[2], local[3]); + } else if (locallen == 16) { + logger_log(conn->raop->logger, LOGGER_INFO, + "Local: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + local[0], local[1], local[2], local[3], local[4], local[5], local[6], local[7], + local[8], local[9], local[10], local[11], local[12], local[13], local[14], local[15]); + } + if (remotelen == 4) { + logger_log(conn->raop->logger, LOGGER_INFO, + "Remote: %d.%d.%d.%d", + remote[0], remote[1], remote[2], remote[3]); + } else if (remotelen == 16) { + logger_log(conn->raop->logger, LOGGER_INFO, + "Remote: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + remote[0], remote[1], remote[2], remote[3], remote[4], remote[5], remote[6], remote[7], + remote[8], remote[9], remote[10], remote[11], remote[12], remote[13], remote[14], remote[15]); + } + + conn->local = malloc(locallen); + assert(conn->local); + memcpy(conn->local, local, locallen); + + conn->remote = malloc(remotelen); + assert(conn->remote); + memcpy(conn->remote, remote, remotelen); + + conn->locallen = locallen; + conn->remotelen = remotelen; + + if (raop->callbacks.conn_init) { + raop->callbacks.conn_init(raop->callbacks.cls); + } + + return conn; +} + +static void +conn_request(void *ptr, http_request_t *request, http_response_t **response) { + raop_conn_t *conn = ptr; + logger_log(conn->raop->logger, LOGGER_DEBUG, "conn_request"); + const char *method; + const char *url; + const char *cseq; + + char *response_data = NULL; + int response_datalen = 0; + + method = http_request_get_method(request); + url = http_request_get_url(request); + cseq = http_request_get_header(request, "CSeq"); + if (!method || !cseq) { + return; + } + + *response = http_response_init("RTSP/1.0", 200, "OK"); + + http_response_add_header(*response, "CSeq", cseq); + //http_response_add_header(*response, "Apple-Jack-Status", "connected; type=analog"); + http_response_add_header(*response, "Server", "AirTunes/220.68"); + + logger_log(conn->raop->logger, LOGGER_DEBUG, "Handling request %s with URL %s", method, url); + raop_handler_t handler = NULL; + if (!strcmp(method, "GET") && !strcmp(url, "/info")) { + handler = &raop_handler_info; + } else if (!strcmp(method, "POST") && !strcmp(url, "/pair-setup")) { + handler = &raop_handler_pairsetup; + } else if (!strcmp(method, "POST") && !strcmp(url, "/pair-verify")) { + handler = &raop_handler_pairverify; + } else if (!strcmp(method, "POST") && !strcmp(url, "/fp-setup")) { + handler = &raop_handler_fpsetup; + } else if (!strcmp(method, "OPTIONS")) { + handler = &raop_handler_options; + } else if (!strcmp(method, "SETUP")) { + handler = &raop_handler_setup; + } else if (!strcmp(method, "GET_PARAMETER")) { + handler = &raop_handler_get_parameter; + } else if (!strcmp(method, "SET_PARAMETER")) { + handler = &raop_handler_set_parameter; + } else if (!strcmp(method, "POST") && !strcmp(url, "/feedback")) { + handler = &raop_handler_feedback; + } else if (!strcmp(method, "RECORD")) { + handler = &raop_handler_record; + } else if (!strcmp(method, "FLUSH")) { + const char *rtpinfo; + int next_seq = -1; + + rtpinfo = http_request_get_header(request, "RTP-Info"); + if (rtpinfo) { + logger_log(conn->raop->logger, LOGGER_DEBUG, "Flush with RTP-Info: %s", rtpinfo); + if (!strncmp(rtpinfo, "seq=", 4)) { + next_seq = strtol(rtpinfo + 4, NULL, 10); + } + } + if (conn->raop_rtp) { + raop_rtp_flush(conn->raop_rtp, next_seq); + } else { + logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at FLUSH"); + } + } else if (!strcmp(method, "TEARDOWN")) { + //http_response_add_header(*response, "Connection", "close"); + if (conn->raop_rtp != NULL && raop_rtp_is_running(conn->raop_rtp)) { + /* Destroy our RTP session */ + raop_rtp_stop(conn->raop_rtp); + } else if (conn->raop_rtp_mirror) { + /* Destroy our sessions */ + raop_rtp_destroy(conn->raop_rtp); + conn->raop_rtp = NULL; + raop_rtp_mirror_destroy(conn->raop_rtp_mirror); + conn->raop_rtp_mirror = NULL; + } + } + if (handler != NULL) { + handler(conn, request, *response, &response_data, &response_datalen); + } + http_response_finish(*response, response_data, response_datalen); + if (response_data) { + free(response_data); + response_data = NULL; + response_datalen = 0; + } +} + +static void +conn_destroy(void *ptr) { + raop_conn_t *conn = ptr; + + logger_log(conn->raop->logger, LOGGER_INFO, "Destroying connection"); + + if (conn->raop->callbacks.conn_destroy) { + conn->raop->callbacks.conn_destroy(conn->raop->callbacks.cls); + } + + if (conn->raop_ntp) { + raop_ntp_destroy(conn->raop_ntp); + } + if (conn->raop_rtp) { + /* This is done in case TEARDOWN was not called */ + raop_rtp_destroy(conn->raop_rtp); + } + if (conn->raop_rtp_mirror) { + /* This is done in case TEARDOWN was not called */ + raop_rtp_mirror_destroy(conn->raop_rtp_mirror); + } + + conn->raop->callbacks.video_flush(conn->raop->callbacks.cls); + + free(conn->local); + free(conn->remote); + pairing_session_destroy(conn->pairing); + fairplay_destroy(conn->fairplay); + free(conn); +} + +raop_t * +raop_init(int max_clients, raop_callbacks_t *callbacks) { + raop_t *raop; + pairing_t *pairing; + httpd_t *httpd; + httpd_callbacks_t httpd_cbs; + + assert(callbacks); + assert(max_clients > 0); + assert(max_clients < 100); + + /* Initialize the network */ + if (netutils_init() < 0) { + return NULL; + } + + /* Validate the callbacks structure */ + if (!callbacks->audio_process || + !callbacks->video_process) { + return NULL; + } + + /* Allocate the raop_t structure */ + raop = calloc(1, sizeof(raop_t)); + if (!raop) { + return NULL; + } + + /* Initialize the logger */ + raop->logger = logger_init(); + pairing = pairing_init_generate(); + if (!pairing) { + free(raop); + return NULL; + } + + /* Set HTTP callbacks to our handlers */ + memset(&httpd_cbs, 0, sizeof(httpd_cbs)); + httpd_cbs.opaque = raop; + httpd_cbs.conn_init = &conn_init; + httpd_cbs.conn_request = &conn_request; + httpd_cbs.conn_destroy = &conn_destroy; + + /* Initialize the http daemon */ + httpd = httpd_init(raop->logger, &httpd_cbs, max_clients); + if (!httpd) { + pairing_destroy(pairing); + free(raop); + return NULL; + } + /* Copy callbacks structure */ + memcpy(&raop->callbacks, callbacks, sizeof(raop_callbacks_t)); + raop->pairing = pairing; + raop->httpd = httpd; + + /* initialize network port list */ + raop->port = 0; + raop->timing_lport = 0; + raop->control_lport = 0; + raop->data_lport = 0; + raop->mirror_data_lport = 0; + + /* initialize display width, height, refresh_rate, max_fps */ + raop->display_width = 1920; + raop->display_height = 1080; + raop->display_refresh_rate = 60; + raop->display_max_fps = 30; + + return raop; +} + +void +raop_destroy(raop_t *raop) { + if (raop) { + raop_stop(raop); + pairing_destroy(raop->pairing); + httpd_destroy(raop->httpd); + logger_destroy(raop->logger); + free(raop); + + /* Cleanup the network */ + netutils_cleanup(); + } +} + +int +raop_is_running(raop_t *raop) { + assert(raop); + + return httpd_is_running(raop->httpd); +} + +void +raop_set_log_level(raop_t *raop, int level) { + assert(raop); + + logger_set_level(raop->logger, level); +} + +void raop_set_display(raop_t *raop, unsigned short width, unsigned short height, + unsigned short refresh_rate, unsigned short max_fps){ + assert(raop); + + if (width) raop->display_width = width; + if (height) raop->display_height = height; + + // these must fit into a single byte + if (refresh_rate > 255) refresh_rate = 255; + if (max_fps > 255) max_fps = 255; + + if (refresh_rate) raop->display_refresh_rate = refresh_rate; + if (max_fps) raop->display_max_fps = max_fps; + + if (raop->display_max_fps > raop->display_refresh_rate) { + raop->display_max_fps = raop->display_refresh_rate; + } +} + +void +raop_set_port(raop_t *raop, unsigned short port) { + assert(raop); + raop->port = port; +} + +void +raop_set_udp_ports(raop_t *raop, unsigned short udp[3]) { + assert(raop); + raop->timing_lport = udp[0]; + raop->control_lport = udp[1]; + raop->data_lport = udp[2]; +} + +void +raop_set_tcp_ports(raop_t *raop, unsigned short tcp[2]) { + assert(raop); + raop->mirror_data_lport = tcp[0]; + raop->port = tcp[1]; +} + +unsigned short +raop_get_port(raop_t *raop) { + assert(raop); + return raop->port; +} + +void * +raop_get_callback_cls(raop_t *raop) { + assert(raop); + return raop->callbacks.cls; +} + +void +raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls) { + assert(raop); + + logger_set_callback(raop->logger, callback, cls); +} + +void +raop_set_dnssd(raop_t *raop, dnssd_t *dnssd) { + assert(dnssd); + raop->dnssd = dnssd; +} + + +int +raop_start(raop_t *raop, unsigned short *port) { + assert(raop); + assert(port); + return httpd_start(raop->httpd, port); +} + +void +raop_stop(raop_t *raop) { + assert(raop); + httpd_stop(raop->httpd); } -#endif -#endif diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index ab4396d..0c27a83 100755 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -102,20 +102,20 @@ raop_handler_info(raop_conn_t *conn, plist_t audio_latencies_node = plist_new_array(); plist_t audio_latencies_0_node = plist_new_dict(); - plist_t audio_latencies_0_output_latency_micros_node = plist_new_bool(0); + plist_t audio_latencies_0_output_latency_micros_node = plist_new_uint(0); plist_t audio_latencies_0_type_node = plist_new_uint(100); plist_t audio_latencies_0_audio_type_node = plist_new_string("default"); - plist_t audio_latencies_0_input_latency_micros_node = plist_new_bool(0); + plist_t audio_latencies_0_input_latency_micros_node = plist_new_uint(0); plist_dict_set_item(audio_latencies_0_node, "outputLatencyMicros", audio_latencies_0_output_latency_micros_node); plist_dict_set_item(audio_latencies_0_node, "type", audio_latencies_0_type_node); plist_dict_set_item(audio_latencies_0_node, "audioType", audio_latencies_0_audio_type_node); plist_dict_set_item(audio_latencies_0_node, "inputLatencyMicros", audio_latencies_0_input_latency_micros_node); plist_array_append_item(audio_latencies_node, audio_latencies_0_node); plist_t audio_latencies_1_node = plist_new_dict(); - plist_t audio_latencies_1_output_latency_micros_node = plist_new_bool(0); + plist_t audio_latencies_1_output_latency_micros_node = plist_new_uint(0); plist_t audio_latencies_1_type_node = plist_new_uint(101); plist_t audio_latencies_1_audio_type_node = plist_new_string("default"); - plist_t audio_latencies_1_input_latency_micros_node = plist_new_bool(0); + plist_t audio_latencies_1_input_latency_micros_node = plist_new_uint(0); plist_dict_set_item(audio_latencies_1_node, "outputLatencyMicros", audio_latencies_1_output_latency_micros_node); plist_dict_set_item(audio_latencies_1_node, "type", audio_latencies_1_type_node); plist_dict_set_item(audio_latencies_1_node, "audioType", audio_latencies_1_audio_type_node); @@ -134,13 +134,13 @@ raop_handler_info(raop_conn_t *conn, plist_t displays_0_uuid_node = plist_new_string("e0ff8a27-6738-3d56-8a16-cc53aacee925"); plist_t displays_0_width_physical_node = plist_new_uint(0); plist_t displays_0_height_physical_node = plist_new_uint(0); - plist_t displays_0_width_node = plist_new_uint(conn->raop->display_width); - plist_t displays_0_height_node = plist_new_uint(conn->raop->display_height); - plist_t displays_0_width_pixels_node = plist_new_uint(conn->raop->display_width); - plist_t displays_0_height_pixels_node = plist_new_uint(conn->raop->display_height); + plist_t displays_0_width_node = plist_new_uint((uint16_t) conn->raop->display_width); + plist_t displays_0_height_node = plist_new_uint((uint16_t) conn->raop->display_height); + plist_t displays_0_width_pixels_node = plist_new_uint((uint16_t) conn->raop->display_width); + plist_t displays_0_height_pixels_node = plist_new_uint((uint16_t) conn->raop->display_height); plist_t displays_0_rotation_node = plist_new_bool(0); - plist_t displays_0_refresh_rate_node = plist_new_uint(conn->raop->display_refresh_rate); - plist_t displays_0_max_fps_node = plist_new_uint(conn->raop->display_max_fps); + plist_t displays_0_refresh_rate_node = plist_new_uint((uint8_t) conn->raop->display_refresh_rate); + plist_t displays_0_max_fps_node = plist_new_uint((uint8_t) conn->raop->display_max_fps); plist_t displays_0_overscanned_node = plist_new_bool(1); plist_t displays_0_features = plist_new_uint(14);