diff --git a/lib/raop.h b/lib/raop.h index 8c50b61..3a7efd9 100644 --- a/lib/raop.h +++ b/lib/raop.h @@ -70,8 +70,9 @@ struct raop_callbacks_s { void (*video_report_size)(void *cls, float *width_source, float *height_source, float *width, float *height); }; typedef struct raop_callbacks_s raop_callbacks_t; -raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const unsigned char *remote_addr, int remote_addr_len, unsigned short timing_rport); - +raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const unsigned char *remote_addr, int remote_addr_len, + unsigned short timing_rport, timing_protocol_t *time_protocol); + RAOP_API raop_t *raop_init(int max_clients, raop_callbacks_t *callbacks); 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); diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index db53965..2f0daec 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -445,13 +445,28 @@ raop_handler_setup(raop_conn_t *conn, " Only AirPlay v1 protocol (using NTP and timing port) is supported"); } } - uint64_t string_len; + uint64_t string_len = 0; const char *timing_protocol; + timing_protocol_t time_protocol; plist_t req_timing_protocol_node = plist_dict_get_item(req_root_node, "timingProtocol"); timing_protocol = plist_get_string_ptr(req_timing_protocol_node, &string_len); - if (strcmp(timing_protocol, "NTP")) { - logger_log(conn->raop->logger, LOGGER_ERR, "Client specified timingProtocol=%s, but timingProtocol= NTP is required here", timing_protocol); - } + if (string_len) { + if (strncmp(timing_protocol, "NTP", string_len) == 0) { + time_protocol = NTP; + } else if (strncmp(timing_protocol, "None", string_len) == 0) { + time_protocol = TP_NONE; + } else { + time_protocol = TP_OTHER; + } + if (time_protocol != NTP) { + logger_log(conn->raop->logger, LOGGER_ERR, "Client specified timingProtocol=%s," + " but timingProtocol= NTP is required here", timing_protocol); + } + } else { + logger_log(conn->raop->logger, LOGGER_DEBUG, "Client did not specify timingProtocol," + " old protocol without offset will be used"); + time_protocol = TP_UNSPECIFIED; + } timing_protocol = NULL; uint64_t timing_rport = 0; plist_t req_timing_port_node = plist_dict_get_item(req_root_node, "timingPort"); @@ -461,14 +476,18 @@ raop_handler_setup(raop_conn_t *conn, if (timing_rport) { logger_log(conn->raop->logger, LOGGER_DEBUG, "timing_rport = %llu", timing_rport); } else { - logger_log(conn->raop->logger, LOGGER_ERR, "Client did not supply timing_rport, may be using unsupported AirPlay2 \"Remote Control\" protocol"); + logger_log(conn->raop->logger, LOGGER_ERR, "Client did not supply timing_rport," + " may be using unsupported AirPlay2 \"Remote Control\" protocol"); } unsigned short timing_lport = conn->raop->timing_lport; - conn->raop_ntp = raop_ntp_init(conn->raop->logger, &conn->raop->callbacks, conn->remote, conn->remotelen, timing_rport); + conn->raop_ntp = raop_ntp_init(conn->raop->logger, &conn->raop->callbacks, conn->remote, + conn->remotelen, (unsigned short) timing_rport, &time_protocol); raop_ntp_start(conn->raop_ntp, &timing_lport, conn->raop->max_ntp_timeouts); - conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey, aesiv); - conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey); + conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, + conn->remote, conn->remotelen, aeskey, aesiv); + conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks, + conn->raop_ntp, conn->remote, conn->remotelen, aeskey); plist_t res_event_port_node = plist_new_uint(conn->raop->port); plist_t res_timing_port_node = plist_new_uint(timing_lport); @@ -498,7 +517,8 @@ raop_handler_setup(raop_conn_t *conn, plist_t stream_id_node = plist_dict_get_item(req_stream_node, "streamConnectionID"); uint64_t stream_connection_id; plist_get_uint_val(stream_id_node, &stream_connection_id); - logger_log(conn->raop->logger, LOGGER_DEBUG, "streamConnectionID (needed for AES-CTR video decryption key and iv): %llu", stream_connection_id); + logger_log(conn->raop->logger, LOGGER_DEBUG, "streamConnectionID (needed for AES-CTR video decryption" + " key and iv): %llu", stream_connection_id); if (conn->raop_rtp_mirror) { raop_rtp_init_mirror_aes(conn->raop_rtp_mirror, &stream_connection_id); diff --git a/lib/raop_ntp.c b/lib/raop_ntp.c index 92eec49..7bf69b2 100644 --- a/lib/raop_ntp.c +++ b/lib/raop_ntp.c @@ -91,6 +91,8 @@ struct raop_ntp_s { // UDP socket int tsock; + + timing_protocol_t time_protocol; }; @@ -140,7 +142,7 @@ raop_ntp_parse_remote_address(raop_ntp_t *raop_ntp, const unsigned char *remote_ } raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const unsigned char *remote_addr, - int remote_addr_len, unsigned short timing_rport) { + int remote_addr_len, unsigned short timing_rport, timing_protocol_t *time_protocol) { raop_ntp_t *raop_ntp; assert(logger); @@ -150,6 +152,7 @@ raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const u if (!raop_ntp) { return NULL; } + raop_ntp->time_protocol = *time_protocol; raop_ntp->logger = logger; memcpy(&raop_ntp->callbacks, callbacks, sizeof(raop_callbacks_t)); raop_ntp->timing_rport = timing_rport; @@ -322,10 +325,10 @@ raop_ntp_thread(void *arg) int64_t t0 = (int64_t) byteutils_get_ntp_timestamp(response, 8); // Local time of the client when the NTP request packet arrives at the client - int64_t t1 = (int64_t) byteutils_get_ntp_timestamp(response, 16); + int64_t t1 = (int64_t) raop_remote_timestamp_to_nano_seconds(raop_ntp, byteutils_get_long_be(response, 16)); // Local time of the client when the response message leaves the client - int64_t t2 = (int64_t) byteutils_get_ntp_timestamp(response, 24); + int64_t t2 = (int64_t) raop_remote_timestamp_to_nano_seconds(raop_ntp, byteutils_get_long_be(response, 24)); if (logger_debug) { char *str = utils_data_to_string(response, response_len, 16); @@ -480,6 +483,12 @@ uint64_t raop_ntp_timestamp_to_nano_seconds(uint64_t ntp_timestamp, bool account return (seconds * SECOND_IN_NSECS) + ((fraction * SECOND_IN_NSECS) >> 32); } +uint64_t raop_remote_timestamp_to_nano_seconds(raop_ntp_t *raop_ntp, uint64_t timestamp) { + uint64_t seconds = ((timestamp >> 32) & 0xffffffff); + if (raop_ntp->time_protocol == NTP) seconds -= SECONDS_FROM_1900_TO_1970; + uint64_t fraction = (timestamp & 0xffffffff); + return (seconds * SECOND_IN_NSECS) + ((fraction * SECOND_IN_NSECS) >> 32); +} /** * Returns the current time in nano seconds according to the local wall clock. * The system Unix time is used as the local wall clock. diff --git a/lib/raop_ntp.h b/lib/raop_ntp.h index dc90cc1..bf2f5bb 100644 --- a/lib/raop_ntp.h +++ b/lib/raop_ntp.h @@ -25,6 +25,7 @@ typedef struct raop_ntp_s raop_ntp_t; +typedef enum timing_protocol_e { NTP, TP_NONE, TP_OTHER, TP_UNSPECIFIED } timing_protocol_t; void raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport, int max_ntp_timeouts); @@ -35,6 +36,7 @@ unsigned short raop_ntp_get_port(raop_ntp_t *raop_ntp); void raop_ntp_destroy(raop_ntp_t *raop_rtp); uint64_t raop_ntp_timestamp_to_nano_seconds(uint64_t ntp_timestamp, bool account_for_epoch_diff); +uint64_t raop_remote_timestamp_to_nano_seconds(raop_ntp_t *raop_ntp, uint64_t timestamp); uint64_t raop_ntp_get_local_time(raop_ntp_t *raop_ntp); uint64_t raop_ntp_get_remote_time(raop_ntp_t *raop_ntp); diff --git a/lib/raop_rtp.c b/lib/raop_rtp.c index f8284b2..e5e9aba 100644 --- a/lib/raop_rtp.c +++ b/lib/raop_rtp.c @@ -557,7 +557,7 @@ raop_rtp_thread_udp(void *arg) have_synced = true; } uint64_t sync_ntp_raw = byteutils_get_long_be(packet, 8); - uint64_t sync_ntp_remote = raop_ntp_timestamp_to_nano_seconds(sync_ntp_raw, true); + uint64_t sync_ntp_remote = raop_remote_timestamp_to_nano_seconds(raop_rtp->ntp, sync_ntp_raw); if (logger_debug) { uint64_t sync_ntp_local = raop_ntp_convert_remote_time(raop_rtp->ntp, sync_ntp_remote); char *str = utils_data_to_string(packet, packetlen, 20); diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c index 75349c5..87b562d 100644 --- a/lib/raop_rtp_mirror.c +++ b/lib/raop_rtp_mirror.c @@ -321,11 +321,13 @@ raop_rtp_mirror_thread(void *arg) /* packet[4] + packet[5] identify the payload type: values seen are: * * 0x00 0x00: encrypted packet containing a non-IDR type 1 VCL NAL unit * * 0x00 0x10: encrypted packet containing an IDR type 5 VCL NAL unit * - * 0x01 0x00 unencrypted packet containing a type 7 SPS NAL + a type 8 PPS NAL unit * + * 0x01 0x00: unencrypted packet containing a type 7 SPS NAL + a type 8 PPS NAL unit * + * 0x02 0x00: unencryted packet (old protocol) no payload, sent once every second * * 0x05 0x00 unencrypted packet with a "streaming report", sent once per second. */ /* packet[6] + packet[7] may list a payload "option": values seen are: * * 0x00 0x00 : encrypted and "streaming report" packets * + * 0x1e 0x00 : old protocol (seen in AirMyPC) no-payload once-per-second packets * * 0x16 0x01 : seen in most unencrypted SPS+PPS packets * * 0x56 0x01 : occasionally seen in unencrypted SPS+PPS packets (why different?) */ @@ -400,14 +402,20 @@ raop_rtp_mirror_thread(void *arg) * that has not yet been sent. This will trigger prepending it to the current NAL, and the prepend_sps_pps * flag will be set to false after it has been prepended. */ + if (prepend_sps_pps & (ntp_timestamp_raw != ntp_timestamp_nal)) { + logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, + "raop_rtp_mirror: prepended sps_pps timestamp does not match timestamp of " + "video payload\n%llu\n%llu , discarding", ntp_timestamp_raw, ntp_timestamp_nal); + free (sps_pps); + sps_pps = NULL; + prepend_sps_pps = false; + } + if (prepend_sps_pps) { assert(sps_pps); payload_out = (unsigned char*) malloc(payload_size + sps_pps_len); payload_decrypted = payload_out + sps_pps_len; if (ntp_timestamp_raw != ntp_timestamp_nal) { - logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, - "raop_rtp_mirror: prepended sps_pps timestamp does not match timestamp of " - "video payload\n%llu\n%llu", ntp_timestamp_raw, ntp_timestamp_nal); } memcpy(payload_out, sps_pps, sps_pps_len); free (sps_pps); @@ -441,6 +449,7 @@ raop_rtp_mirror_thread(void *arg) int nalu_type = payload_decrypted[nalu_size] & 0x1f; int ref_idc = (payload_decrypted[nalu_size] >> 5); switch (nalu_type) { + case 14: /* Prefix NALu , seen before all VCL Nalu's in AirMyPc */ case 5: /*IDR, slice_layer_without_partitioning */ case 1: /*non-IDR, slice_layer_without_partitioning */ break; @@ -461,6 +470,24 @@ raop_rtp_mirror_thread(void *arg) free(str); } break; + case 7: + if (logger_debug) { + char *str = utils_data_to_string(payload_decrypted + nalu_size, nc_len, 16); + logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror SPS NAL size = %d", nc_len); + logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, + "raop_rtp_mirror h264 Sequence Parameter Set:\n%s", str); + free(str); + } + break; + case 8: + if (logger_debug) { + char *str = utils_data_to_string(payload_decrypted + nalu_size, nc_len, 16); + logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror PPS NAL size = %d", nc_len); + logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, + "raop_rtp_mirror h264 Picture Parameter Set :\n%s", str); + free(str); + } + break; default: logger_log(raop_rtp_mirror->logger, LOGGER_INFO, "unexpected non-VCL NAL unit: nalu_type = %d, ref_idc = %d, nalu_size = %d," @@ -580,6 +607,10 @@ raop_rtp_mirror_thread(void *arg) // memcpy(h264.picture_parameter_set, picture_parameter_set, pps_size); break; + case 0x02: + logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "\nReceived old-protocol once-per-second packet from client:" + " payload_size %d header %s ts_raw = %llu", payload_size, packet_description, ntp_timestamp_raw); + /* "old protocol" (used by AirMyPC), rest of 128-byte packet is empty */ case 0x05: logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "\nReceived video streaming performance info packet from client:" " payload_size %d header %s ts_raw = %llu", payload_size, packet_description, ntp_timestamp_raw); @@ -615,7 +646,7 @@ raop_rtp_mirror_thread(void *arg) break; default: logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "\nReceived unexpected TCP packet from client, " - "size %d, %s ts_raw = raw%llu", payload_size, packet_description, ntp_timestamp_raw); + "size %d, %s ts_raw = %llu", payload_size, packet_description, ntp_timestamp_raw); break; }