/* * Copyright (c) 2019 dsafa22, All Rights Reserved. * * 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. * *================================================================== * modified by fduncanh 2021-2023 */ #include "raop_rtp_mirror.h" #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #endif #include "raop.h" #include "netutils.h" #include "compat.h" #include "logger.h" #include "byteutils.h" #include "mirror_buffer.h" #include "stream.h" #include "utils.h" #include "plist/plist.h" #ifdef _WIN32 #define CAST (char *) /* are these keepalive settings for WIN32 correct? */ /* (taken from https://github.com/wegank/ludimus) */ #define TCP_KEEPALIVE 3 #define TCP_KEEPCNT 16 #define TCP_KEEPIDLE TCP_KEEPALIVE #define TCP_KEEPINTVL 17 #else #define CAST #endif #define SECOND_IN_NSECS 1000000000UL #define SEC SECOND_IN_NSECS /* for MacOS, where SOL_TCP and TCP_KEEPIDLE are not defined */ #if !defined(SOL_TCP) && defined(IPPROTO_TCP) #define SOL_TCP IPPROTO_TCP #endif #if !defined(TCP_KEEPIDLE) && defined(TCP_KEEPALIVE) #define TCP_KEEPIDLE TCP_KEEPALIVE #endif //struct h264codec_s { // unsigned char compatibility; // short pps_size; // short sps_size; // unsigned char level; // unsigned char number_of_pps; // unsigned char* picture_parameter_set; // unsigned char profile_high; // unsigned char reserved_3_and_sps; // unsigned char reserved_6_and_nal; // unsigned char* sequence_parameter_set; // unsigned char version; //}; struct raop_rtp_mirror_s { logger_t *logger; raop_callbacks_t callbacks; raop_ntp_t *ntp; /* mirror buffer for decryption */ mirror_buffer_t *buffer; /* Remote address as sockaddr */ struct sockaddr_storage remote_saddr; socklen_t remote_saddr_len; /* MUTEX LOCKED VARIABLES START */ /* These variables only edited mutex locked */ int running; int joined; int flush; thread_handle_t thread_mirror; mutex_handle_t run_mutex; /* MUTEX LOCKED VARIABLES END */ int mirror_data_sock; unsigned short mirror_data_lport; /* switch for displaying client FPS data */ uint8_t show_client_FPS_data; }; static int raop_rtp_mirror_parse_remote(raop_rtp_mirror_t *raop_rtp_mirror, const char *remote, int remotelen) { int family; int ret; assert(raop_rtp_mirror); if (remotelen == 4) { family = AF_INET; } else if (remotelen == 16) { family = AF_INET6; } else { return -1; } logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror parse remote ip = %s", remote); ret = netutils_parse_address(family, remote, &raop_rtp_mirror->remote_saddr, sizeof(raop_rtp_mirror->remote_saddr)); if (ret < 0) { return -1; } raop_rtp_mirror->remote_saddr_len = ret; return 0; } #define NO_FLUSH (-42) raop_rtp_mirror_t *raop_rtp_mirror_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, const char *remote, int remotelen, const unsigned char *aeskey) { raop_rtp_mirror_t *raop_rtp_mirror; assert(logger); assert(callbacks); raop_rtp_mirror = calloc(1, sizeof(raop_rtp_mirror_t)); if (!raop_rtp_mirror) { return NULL; } raop_rtp_mirror->logger = logger; raop_rtp_mirror->ntp = ntp; memcpy(&raop_rtp_mirror->callbacks, callbacks, sizeof(raop_callbacks_t)); raop_rtp_mirror->buffer = mirror_buffer_init(logger, aeskey); if (!raop_rtp_mirror->buffer) { free(raop_rtp_mirror); return NULL; } if (raop_rtp_mirror_parse_remote(raop_rtp_mirror, remote, remotelen) < 0) { free(raop_rtp_mirror); return NULL; } raop_rtp_mirror->running = 0; raop_rtp_mirror->joined = 1; raop_rtp_mirror->flush = NO_FLUSH; MUTEX_CREATE(raop_rtp_mirror->run_mutex); return raop_rtp_mirror; } void raop_rtp_mirror_init_aes(raop_rtp_mirror_t *raop_rtp_mirror, uint64_t *streamConnectionID) { mirror_buffer_init_aes(raop_rtp_mirror->buffer, streamConnectionID); } #define RAOP_PACKET_LEN 32768 /** * Mirror */ static THREAD_RETVAL raop_rtp_mirror_thread(void *arg) { raop_rtp_mirror_t *raop_rtp_mirror = arg; assert(raop_rtp_mirror); int stream_fd = -1; unsigned char packet[128]; memset(packet, 0 , 128); unsigned char* sps_pps = NULL; bool prepend_sps_pps = false; int sps_pps_len = 0; unsigned char* payload = NULL; unsigned int readstart = 0; bool conn_reset = false; uint64_t ntp_timestamp_nal = 0; uint64_t ntp_timestamp_raw = 0; uint64_t ntp_timestamp_remote = 0; uint64_t ntp_timestamp_local = 0; unsigned char nal_start_code[4] = { 0x00, 0x00, 0x00, 0x01 }; bool logger_debug = (logger_get_level(raop_rtp_mirror->logger) >= LOGGER_DEBUG); bool h265_video = false; video_codec_t codec; const char h264[] = "h264"; const char h265[] = "h265"; bool unsupported_codec = false; bool video_stream_suspended = false; while (1) { fd_set rfds; struct timeval tv; int nfds, ret; MUTEX_LOCK(raop_rtp_mirror->run_mutex); if (!raop_rtp_mirror->running) { MUTEX_UNLOCK(raop_rtp_mirror->run_mutex); logger_log(raop_rtp_mirror->logger, LOGGER_INFO, "raop_rtp_mirror->running is no longer true"); break; } MUTEX_UNLOCK(raop_rtp_mirror->run_mutex); /* Set timeout valu to 5ms */ tv.tv_sec = 0; tv.tv_usec = 5000; /* Get the correct nfds value and set rfds */ FD_ZERO(&rfds); if (stream_fd == -1) { FD_SET(raop_rtp_mirror->mirror_data_sock, &rfds); nfds = raop_rtp_mirror->mirror_data_sock+1; } else { FD_SET(stream_fd, &rfds); nfds = stream_fd+1; } ret = select(nfds, &rfds, NULL, NULL, &tv); if (ret == 0) { /* Timeout happened */ continue; } else if (ret == -1) { logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror error in select"); break; } if (stream_fd == -1 && (raop_rtp_mirror && raop_rtp_mirror->mirror_data_sock >= 0) && FD_ISSET(raop_rtp_mirror->mirror_data_sock, &rfds)) { struct sockaddr_storage saddr; socklen_t saddrlen; logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror accepting client"); saddrlen = sizeof(saddr); stream_fd = accept(raop_rtp_mirror->mirror_data_sock, (struct sockaddr *)&saddr, &saddrlen); if (stream_fd == -1) { int sock_err = SOCKET_GET_ERROR(); logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror error in accept %d %s", sock_err, SOCKET_ERROR_STRING(sock_err)); break; } // We're calling recv for a certain amount of data, so we need a timeout struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 5000; if (setsockopt(stream_fd, SOL_SOCKET, SO_RCVTIMEO, CAST &tv, sizeof(tv)) < 0) { int sock_err = SOCKET_GET_ERROR(); logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror could not set stream socket timeout %d %s", sock_err, SOCKET_ERROR_STRING(sock_err)); break; } int option; option = 1; if (setsockopt(stream_fd, SOL_SOCKET, SO_KEEPALIVE, CAST &option, sizeof(option)) < 0) { int sock_err = SOCKET_GET_ERROR(); logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "raop_rtp_mirror could not set stream socket keepalive %d %s", sock_err, SOCKET_ERROR_STRING(sock_err)); } option = 60; if (setsockopt(stream_fd, SOL_TCP, TCP_KEEPIDLE, CAST &option, sizeof(option)) < 0) { int sock_err = SOCKET_GET_ERROR(); logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "raop_rtp_mirror could not set stream socket keepalive time %d %s", sock_err, SOCKET_ERROR_STRING(sock_err)); } option = 10; if (setsockopt(stream_fd, SOL_TCP, TCP_KEEPINTVL, CAST &option, sizeof(option)) < 0) { int sock_err = SOCKET_GET_ERROR(); logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "raop_rtp_mirror could not set stream socket keepalive interval %d %s", sock_err, SOCKET_ERROR_STRING(sock_err)); } option = 6; if (setsockopt(stream_fd, SOL_TCP, TCP_KEEPCNT, CAST &option, sizeof(option)) < 0) { int sock_err = SOCKET_GET_ERROR(); logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "raop_rtp_mirror could not set stream socket keepalive probes %d %s", sock_err, SOCKET_ERROR_STRING(sock_err)); } readstart = 0; } if (stream_fd != -1 && FD_ISSET(stream_fd, &rfds)) { // The first 128 bytes are some kind of header for the payload that follows while (payload == NULL && readstart < 128) { unsigned char* pos = packet + readstart; ret = recv(stream_fd, CAST pos, 128 - readstart, 0); if (ret <= 0) break; readstart = readstart + ret; } if (payload == NULL && ret == 0) { logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror tcp socket was closed by client (recv returned 0); got %d bytes of 128 byte header",readstart); FD_CLR(stream_fd, &rfds); stream_fd = -1; continue; } else if (payload == NULL && ret == -1) { int sock_err = SOCKET_GET_ERROR(); if (sock_err == SOCKET_ERRORNAME(EAGAIN) || sock_err == SOCKET_ERRORNAME(EWOULDBLOCK)) continue; // Timeouts can happen even if the connection is fine logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror error in header recv: %d %s", sock_err, SOCKET_ERROR_STRING(sock_err)); if (sock_err == SOCKET_ERRORNAME(ECONNRESET)) conn_reset = true;; break; } /*packet[0:3] contains the payload size */ int payload_size = byteutils_get_int(packet, 0); char packet_description[13] = {0}; char *p = packet_description; int n = sizeof(packet_description); for (int i = 4; i < 8; i++) { snprintf(p, n, "%2.2x ", (unsigned int) packet[i]); n -= 3; p += 3; } ntp_timestamp_raw = byteutils_get_long(packet, 8); ntp_timestamp_remote = raop_ntp_timestamp_to_nano_seconds(ntp_timestamp_raw, false); /* 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 * * 0x02 0x00: unencrypted 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 h264 SPS+PPS packets * * 0x56 0x01 : unencrypted h264 SPS+PPS packets (video stream stops, client sleeps) * * 0x1e 0x01 : unencrypted h265/HEVC SPS+PPS packets * 0x5e 0x01 : unencrypted h265 SPS+PPS packets (video stream stops, client sleeps) */ /* unencrypted packets with a SPS and a PPS NAL are sent initially, and also when a * * change in video format (e.g. width, height) subsequently occurs. They seem always * * to be followed by a packet with a type 5 encrypted IDR VCL NAL, with an identical * * timestamp. On M1/M2 Mac clients, this type 5 NAL is prepended with a type 6 SEI * * NAL unit. Here we prepend the SPS+PPS NALs to the next encrypted packet, which * * always has the same timestamp, and is (almost?) always an IDR NAL unit. */ /* Unencrypted SPS/PPS packets also have image-size data in (parts of) packet[16:127] */ /* "streaming report" packets have no timestamp in packet[8:15] */ if (payload == NULL) { payload = malloc(payload_size); readstart = 0; } while (readstart < payload_size) { // Payload data unsigned char *pos = payload + readstart; ret = recv(stream_fd, CAST pos, payload_size - readstart, 0); if (ret <= 0) break; readstart = readstart + ret; } if (ret == 0) { logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror tcp socket was closed by client (recv returned 0)"); break; } else if (ret == -1) { int sock_err = SOCKET_GET_ERROR(); if (sock_err == SOCKET_ERRORNAME(EAGAIN) || sock_err == SOCKET_ERRORNAME(EWOULDBLOCK)) continue; // Timeouts can happen even if the connection is fine logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror error in recv: %d %s", sock_err, SOCKET_ERROR_STRING(sock_err)); if (errno == SOCKET_ERRORNAME(ECONNRESET)) conn_reset = true; break; } switch (packet[4]) { case 0x00: // Normal video data (VCL NAL) // Conveniently, the video data is already stamped with the remote wall clock time, // so no additional clock syncing needed. The only thing odd here is that the video // ntp time stamps don't include the SECONDS_FROM_1900_TO_1970, so it's really just // counting nano seconds since last boot. ntp_timestamp_local = raop_ntp_convert_remote_time(raop_rtp_mirror->ntp, ntp_timestamp_remote); if (logger_debug) { uint64_t ntp_now = raop_ntp_get_local_time(raop_rtp_mirror->ntp); int64_t latency = ((int64_t) ntp_now) - ((int64_t) ntp_timestamp_local); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp video: now = %8.6f, ntp = %8.6f, latency = %8.6f, ts = %8.6f, %s %s", (double) ntp_now / SEC, (double) ntp_timestamp_local / SEC, (double) latency / SEC, (double) ntp_timestamp_remote / SEC, packet_description, h265_video ? h265 : h264); } unsigned char* payload_out; unsigned char* payload_decrypted; /* * nal_types:1 Coded non-partitioned slice of a non-IDR picture * 5 Coded non-partitioned slice of an IDR picture * 6 Supplemental enhancement information (SEI) * 7 Sequence parameter set (SPS) * 8 Picture parameter set (PPS) * * if a previous unencrypted packet contains an SPS (type 7) and PPS (type 8) NAL which has not * yet been sent, it should be prepended to the current NAL. The M1 Macs have increased the h264 level, * and now the first encrypted packet after the unencrypted SPS+PPS packet may also contain a SEI (type 6) NAL * prepended to its VCL NAL. * * The flag prepend_sps_pps = true will signal that the previous packet contained a SPS NAL + a PPS NAL, * 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; memcpy(payload_out, sps_pps, sps_pps_len); free (sps_pps); sps_pps = NULL; } else { payload_out = (unsigned char*) malloc(payload_size); payload_decrypted = payload_out; } // Decrypt data mirror_buffer_decrypt(raop_rtp_mirror->buffer, payload, payload_decrypted, payload_size); // It seems the AirPlay protocol prepends NALs with their size, which we're replacing with the 4-byte // start code for the NAL Byte-Stream Format. bool valid_data = true; int nalu_size = 0; int nalus_count = 0; while (nalu_size < payload_size) { int nc_len = byteutils_get_int_be(payload_decrypted, nalu_size); if (nc_len < 0 || nalu_size + 4 > payload_size) { valid_data = false; break; } memcpy(payload_decrypted + nalu_size, nal_start_code, 4); nalu_size += 4; nalus_count++; /* first bit of h264 nalu MUST be 0 ("forbidden_zero_bit") */ if (payload_decrypted[nalu_size] & 0x80) { valid_data = false; break; } int nalu_type; if (h265_video) { nalu_type = payload_decrypted[nalu_size] & 0x7e >> 1;; //logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG," h265 video, NALU type %d, size %d", nalu_type, nc_len); } else { 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; case 2: /* slice data partition A */ case 3: /* slice data partition B */ case 4: /* slice data partition C */ logger_log(raop_rtp_mirror->logger, LOGGER_INFO, "unexpected partitioned VCL NAL unit: nalu_type = %d, ref_idc = %d, nalu_size = %d," "processed bytes %d, payloadsize = %d nalus_count = %d", nalu_type, ref_idc, nc_len, nalu_size, payload_size, nalus_count); break; case 6: 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 SEI NAL size = %d", nc_len); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror h264 Supplemental Enhancement Information:\n%s", str); 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," "processed bytes %d, payloadsize = %d nalus_count = %d", nalu_type, ref_idc, nc_len, nalu_size, payload_size, nalus_count); break; } } nalu_size += nc_len; } if (nalu_size != payload_size) valid_data = false; if(!valid_data) { logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "nalu marked as invalid"); payload_out[0] = 1; /* mark video data as invalid h264 (failed decryption) */ } payload_decrypted = NULL; video_decode_struct video_data; video_data.is_h265 = h265_video; video_data.ntp_time_local = ntp_timestamp_local; video_data.ntp_time_remote = ntp_timestamp_remote; video_data.nal_count = nalus_count; /*nal_count will be the number of nal units in the packet */ video_data.data_len = payload_size; video_data.data = payload_out; if (prepend_sps_pps) { video_data.data_len += sps_pps_len; video_data.nal_count += 2; if (h265_video) { video_data.nal_count++; } prepend_sps_pps = false; } raop_rtp_mirror->callbacks.video_process(raop_rtp_mirror->callbacks.cls, raop_rtp_mirror->ntp, &video_data); free(payload_out); break; case 0x01: /* 128-byte observed packet header structure bytes 0-15: length + timestamp bytes 16-19 float width_source (value is x.0000, x = unsigned short) bytes 20-23 float height_source (value is x.0000, x = unsigned short) bytes 24-39 all 0x0 bytes 40-43 float width_source (value is x.0000, x = unsigned short) bytes 44-47 float height_source (value is x.0000, x = unsigned short) bytes 48-51 ??? float "other_w" (value seems to be x.0000, x = unsigned short) bytes 48-51 ??? float "other_h" (value seems to be x.0000, x = unsigned short) bytes 56-59 width bytes 60-63 height bytes 64-127 all 0x0 */ // The information in the payload contains an SPS and a PPS NAL // The sps_pps is not encrypted logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "\nReceived unencrypted codec packet from client:" " payload_size %d header %s ts_client = %8.6f", payload_size, packet_description, (double) ntp_timestamp_remote / SEC); if (!video_stream_suspended && (packet[6] == 0x56 || packet[6] == 0x5e)) { video_stream_suspended = true; raop_rtp_mirror->callbacks.video_pause(raop_rtp_mirror->callbacks.cls); } else if (video_stream_suspended && (packet[6] == 0x16 || packet[6] == 0x1e)) { raop_rtp_mirror->callbacks.video_resume(raop_rtp_mirror->callbacks.cls); video_stream_suspended = false; } codec = VIDEO_CODEC_UNKNOWN; assert (raop_rtp_mirror->callbacks.video_set_codec); ntp_timestamp_nal = ntp_timestamp_raw; /* these "floats" are in fact integers that fit into unsigned shorts */ float width_0 = byteutils_get_float(packet, 16); float height_0 = byteutils_get_float(packet, 20); float width_source = byteutils_get_float(packet, 40); // duplication of width_0 float height_source = byteutils_get_float(packet, 44); // duplication of height_0 float unknown_w = byteutils_get_float(packet, 48); float unknown_h = byteutils_get_float(packet, 52); float width = byteutils_get_float(packet, 56); float height = byteutils_get_float(packet, 60); if (width != width_0 || height != height_0) { logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror: Unexpected : data %f," " %f != width_source = %f, height_source = %f", width_0, height_0, width_source, height_source); } logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror: unidentified extra header data %f, %f", unknown_w, unknown_h); if (raop_rtp_mirror->callbacks.video_report_size) { raop_rtp_mirror->callbacks.video_report_size(raop_rtp_mirror->callbacks.cls, &width_source, &height_source, &width, &height); } logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror width_source = %f height_source = %f width = %f height = %f", width_source, height_source, width, height); if (payload_size == 0) { logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror: received type 0x01 packet with no payload:\n" "this indicates non-h264 video but Airplay features bit 42 (SupportsScreenMultiCodec) is not set\n" "use startup option \"-h265\" to set this bit and support h265 (4K) video"); unsupported_codec = true; break; } if (sps_pps) { free(sps_pps); sps_pps = NULL; } /* test for a H265 VPS/SPS/PPS */ unsigned char hvc1[] = { 0x68, 0x76, 0x63, 0x31 }; if (!memcmp(payload + 4, hvc1, 4)) { /* hvc1 HECV detected */ codec = VIDEO_CODEC_H265; h265_video = true; raop_rtp_mirror->callbacks.video_set_codec(raop_rtp_mirror->callbacks.cls, codec); unsigned char vps_start_code[] = { 0xa0, 0x00, 0x01, 0x00 }; unsigned char sps_start_code[] = { 0xa1, 0x00, 0x01, 0x00 }; unsigned char pps_start_code[] = { 0xa2, 0x00, 0x01, 0x00 }; unsigned char *vps; short vps_size; unsigned char *sps; short sps_size; unsigned char *pps; short pps_size; unsigned char * ptr = payload + 0x75; if (memcmp(ptr, vps_start_code, 4)) { logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "non-conforming HEVC VPS/SPS/PPS payload (VPS)"); raop_rtp_mirror->callbacks.video_pause(raop_rtp_mirror->callbacks.cls); break; } vps_size = byteutils_get_short_be(ptr, 3); ptr += 5; vps = ptr; if (logger_debug) { char *str = utils_data_to_string(vps, vps_size, 16); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "h265 vps size %d\n%s",vps_size, str); free(str); } ptr += vps_size; if (memcmp(ptr, sps_start_code, 4)) { logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "non-conforming HEVC VPS/SPS/PPS payload (SPS)"); raop_rtp_mirror->callbacks.video_pause(raop_rtp_mirror->callbacks.cls); break; } sps_size = byteutils_get_short_be(ptr, 3); ptr += 5; sps = ptr; if (logger_debug) { char *str = utils_data_to_string(sps, sps_size, 16); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "h265 sps size %d\n%s",vps_size, str); free(str); } ptr += sps_size; if (memcmp(ptr, pps_start_code, 4)) { logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "non-conforming HEVC VPS/SPS/PPS payload (PPS)"); raop_rtp_mirror->callbacks.video_pause(raop_rtp_mirror->callbacks.cls); break; } pps_size = byteutils_get_short_be(ptr, 3); ptr += 5; pps = ptr; if (logger_debug) { char *str = utils_data_to_string(pps, pps_size, 16); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "h265 pps size %d\n%s",pps_size, str); free(str); } sps_pps_len = vps_size + sps_size + pps_size + 12; sps_pps = (unsigned char*) malloc(sps_pps_len); assert(sps_pps); ptr = sps_pps; memcpy(ptr, nal_start_code, 4); ptr += 4; memcpy(ptr, vps, vps_size); ptr += vps_size; memcpy(ptr, nal_start_code, 4); ptr += 4; memcpy(ptr, sps, sps_size); ptr += sps_size; memcpy(ptr, nal_start_code, 4); ptr += 4; memcpy(ptr, pps, pps_size); } else { codec = VIDEO_CODEC_H264; h265_video = false; raop_rtp_mirror->callbacks.video_set_codec(raop_rtp_mirror->callbacks.cls, codec); short sps_size = byteutils_get_short_be(payload,6); unsigned char *sequence_parameter_set = payload + 8; short pps_size = byteutils_get_short_be(payload, sps_size + 9); unsigned char *picture_parameter_set = payload + sps_size + 11; int data_size = 6; if (logger_debug) { char *str = utils_data_to_string(payload, data_size, 16); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror: SPS+PPS header size = %d", data_size); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror h264 SPS+PPS header:\n%s", str); free(str); str = utils_data_to_string(sequence_parameter_set, sps_size,16); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror SPS NAL size = %d", sps_size); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror h264 Sequence Parameter Set:\n%s", str); free(str); str = utils_data_to_string(picture_parameter_set, pps_size, 16); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror PPS NAL size = %d", pps_size); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror h264 Picture Parameter Set:\n%s", str); free(str); } data_size = payload_size - sps_size - pps_size - 11; if (data_size > 0 && logger_debug) { char *str = utils_data_to_string (picture_parameter_set + pps_size, data_size, 16); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "remainder size = %d", data_size); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "remainder of SPS+PPS packet:\n%s", str); free(str); } else if (data_size < 0) { logger_log(raop_rtp_mirror->logger, LOGGER_ERR, " pps_sps error: packet remainder size = %d < 0", data_size); } // Copy the sps and pps into a buffer to prepend to the next NAL unit. sps_pps_len = sps_size + pps_size + 8; sps_pps = (unsigned char*) malloc(sps_pps_len); assert(sps_pps); memcpy(sps_pps, nal_start_code, 4); memcpy(sps_pps + 4, sequence_parameter_set, sps_size); memcpy(sps_pps + sps_size + 4, nal_start_code, 4); memcpy(sps_pps + sps_size + 8, payload + sps_size + 11, pps_size); } prepend_sps_pps = true; // h264codec_t h264; // h264.version = payload[0]; // h264.profile_high = payload[1]; // h264.compatibility = payload[2]; // h264.level = payload[3]; // h264.reserved_6_and_nal = payload[4]; // h264.reserved_3_and_sps = payload[5]; // h264.sps_size = sps_size; // h264.sequence_parameter_set = malloc(h264.sps_size); // memcpy(h264.sequence_parameter_set, sequence_parameter_set, sps_size); // h264.number_of_pps = payload[h264.sps_size + 8]; // h264.pps_size = pps_size; // h264.picture_parameter_set = malloc(h264.pps_size); // 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); /* payloads with packet[4] = 0x05 have no timestamp, and carry video info from the client as a binary plist * * Sometimes (e.g, when the client has a locked screen), there is a 25kB trailer attached to the packet. * * This 25000 Byte trailer with unidentified content seems to be the same data each time it is sent. */ if (payload_size && raop_rtp_mirror->show_client_FPS_data) { //char *str = utils_data_to_string(packet, 128, 16); //logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "type 5 video packet header:\n%s", str); //free (str); int plist_size = payload_size; if (payload_size > 25000) { plist_size = payload_size - 25000; if (logger_debug) { char *str = utils_data_to_string(payload + plist_size, 16, 16); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "video_info packet had 25kB trailer; first 16 bytes are:\n%s", str); free(str); } } if (plist_size) { char *plist_xml; uint32_t plist_len; plist_t root_node = NULL; plist_from_bin((char *) payload, plist_size, &root_node); plist_to_xml(root_node, &plist_xml, &plist_len); logger_log(raop_rtp_mirror->logger, LOGGER_INFO, "%s", plist_xml); free(plist_xml); } } break; default: logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "\nReceived unexpected TCP packet from client, " "size %d, %s ts_raw = %llu", payload_size, packet_description, ntp_timestamp_raw); break; } free(payload); payload = NULL; memset(packet, 0, 128); readstart = 0; if (unsupported_codec) { break; } } } /* Close the stream file descriptor */ if (stream_fd != -1) { closesocket(stream_fd); } // Ensure running reflects the actual state MUTEX_LOCK(raop_rtp_mirror->run_mutex); raop_rtp_mirror->running = false; MUTEX_UNLOCK(raop_rtp_mirror->run_mutex); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror exiting TCP thread"); if (conn_reset && raop_rtp_mirror->callbacks.conn_reset) { const bool video_reset = false; /* leave "frozen video" showing */ raop_rtp_mirror->callbacks.conn_reset(raop_rtp_mirror->callbacks.cls, 0, video_reset); } if (unsupported_codec) { closesocket(raop_rtp_mirror->mirror_data_sock); raop_rtp_mirror_stop(raop_rtp_mirror); raop_rtp_mirror->callbacks.video_reset(raop_rtp_mirror->callbacks.cls); } return 0; } static int raop_rtp_mirror_init_socket(raop_rtp_mirror_t *raop_rtp_mirror, int use_ipv6) { assert(raop_rtp_mirror); unsigned short dport = raop_rtp_mirror->mirror_data_lport; int dsock = netutils_init_socket(&dport, use_ipv6, 0); if (dsock == -1) { goto sockets_cleanup; } /* Listen to the data socket if using TCP */ if (listen(dsock, 1) < 0) { goto sockets_cleanup; } /* Set socket descriptors */ raop_rtp_mirror->mirror_data_sock = dsock; /* Set port values */ raop_rtp_mirror->mirror_data_lport = dport; logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror local data port socket %d port TCP %d", dsock, dport); return 0; sockets_cleanup: if (dsock != -1) closesocket(dsock); return -1; } void raop_rtp_mirror_start(raop_rtp_mirror_t *raop_rtp_mirror, unsigned short *mirror_data_lport, uint8_t show_client_FPS_data) { logger_log(raop_rtp_mirror->logger, LOGGER_INFO, "raop_rtp_mirror starting mirroring"); int use_ipv6 = 0; assert(raop_rtp_mirror); assert(mirror_data_lport); raop_rtp_mirror->show_client_FPS_data = show_client_FPS_data; MUTEX_LOCK(raop_rtp_mirror->run_mutex); if (raop_rtp_mirror->running || !raop_rtp_mirror->joined) { MUTEX_UNLOCK(raop_rtp_mirror->run_mutex); return; } if (raop_rtp_mirror->remote_saddr.ss_family == AF_INET6) { use_ipv6 = 1; } //use_ipv6 = 0; raop_rtp_mirror->mirror_data_lport = *mirror_data_lport; if (raop_rtp_mirror_init_socket(raop_rtp_mirror, use_ipv6) < 0) { logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror initializing socket failed"); MUTEX_UNLOCK(raop_rtp_mirror->run_mutex); return; } *mirror_data_lport = raop_rtp_mirror->mirror_data_lport; /* Create the thread and initialize running values */ raop_rtp_mirror->running = 1; raop_rtp_mirror->joined = 0; THREAD_CREATE(raop_rtp_mirror->thread_mirror, raop_rtp_mirror_thread, raop_rtp_mirror); MUTEX_UNLOCK(raop_rtp_mirror->run_mutex); } void raop_rtp_mirror_stop(raop_rtp_mirror_t *raop_rtp_mirror) { assert(raop_rtp_mirror); /* Check that we are running and thread is not * joined (should never be while still running) */ MUTEX_LOCK(raop_rtp_mirror->run_mutex); if (!raop_rtp_mirror->running || raop_rtp_mirror->joined) { MUTEX_UNLOCK(raop_rtp_mirror->run_mutex); return; } raop_rtp_mirror->running = 0; MUTEX_UNLOCK(raop_rtp_mirror->run_mutex); if (raop_rtp_mirror->mirror_data_sock != -1) { closesocket(raop_rtp_mirror->mirror_data_sock); raop_rtp_mirror->mirror_data_sock = -1; } /* Join the thread */ THREAD_JOIN(raop_rtp_mirror->thread_mirror); /* Mark thread as joined */ MUTEX_LOCK(raop_rtp_mirror->run_mutex); raop_rtp_mirror->joined = 1; MUTEX_UNLOCK(raop_rtp_mirror->run_mutex); } void raop_rtp_mirror_destroy(raop_rtp_mirror_t *raop_rtp_mirror) { if (raop_rtp_mirror) { raop_rtp_mirror_stop(raop_rtp_mirror); MUTEX_DESTROY(raop_rtp_mirror->run_mutex); mirror_buffer_destroy(raop_rtp_mirror->buffer); free(raop_rtp_mirror); } }