diff --git a/src/outputs/rtp_common.c b/src/outputs/rtp_common.c index 8f961812..08bda959 100644 --- a/src/outputs/rtp_common.c +++ b/src/outputs/rtp_common.c @@ -31,17 +31,6 @@ #include #include -#ifdef HAVE_ENDIAN_H -# include -#elif defined(HAVE_SYS_ENDIAN_H) -# include -#elif defined(HAVE_LIBKERN_OSBYTEORDER_H) -#include -#define htobe16(x) OSSwapHostToBigInt16(x) -#define be16toh(x) OSSwapBigToHostInt16(x) -#define htobe32(x) OSSwapHostToBigInt32(x) -#endif - #include #include "logger.h" @@ -55,12 +44,6 @@ #define FRAC 4294967296. // 2^32 as a double #define NTP_EPOCH_DELTA 0x83aa7e80 // 2208988800 - that's 1970 - 1900 in seconds -struct ntp_timestamp -{ - uint32_t sec; - uint32_t frac; -}; - static inline void timespec_to_ntp(struct timespec *ts, struct ntp_timestamp *ns) @@ -289,3 +272,78 @@ rtp_sync_packet_next(struct rtp_session *session, struct rtcp_timestamp cur_stam return &session->sync_packet_next; } +int +rtcp_packet_parse(struct rtcp_packet *pkt, uint8_t *data, size_t size) +{ + if (size < 8) // Must be large enough for at least SSRC + goto packet_malformed; + + memset(pkt, 0, sizeof(struct rtcp_packet)); + + pkt->version = (data[0] & 0xc0) >> 6; // AND 11000000 + if (pkt->version != 2) + goto packet_malformed; + + pkt->padding = (data[0] & 0x20) >> 5; // AND 00100000 + memcpy(&pkt->len, data + 2, 2); + pkt->len = 4 * (be16toh(pkt->len) + 1); // Input len is 32-bit words excl. the 32-bit header + memcpy(&pkt->ssrc, data + 4, 4); + pkt->ssrc = be32toh(pkt->ssrc); + + if (size < pkt->len) + goto packet_malformed; // Possibly a partial read? + + pkt->payload = data + pkt->len; + pkt->payload_len = size - pkt->len; + + pkt->packet_type = data[1]; + // TODO use a switch() + if (pkt->packet_type == RTCP_PACKET_RR) // 201, see RFC 1889 + { + pkt->rr.report_count = data[0] & 0x1f; // AND 00011111 + // TODO check total size of reports is smaller than size? + } + else if (pkt->packet_type == RTCP_PACKET_APP && size >= 12) // 204, see RFC 1889 + { + pkt->app.subtype = data[0] & 0x1f; // AND 00011111 + memcpy(pkt->app.name, data + 8, 4); + } + else if (pkt->packet_type == RTCP_PACKET_PSFB && size >= 12) // 206, see RFC 4585, payload specific feedback + { + pkt->psfb.message_type = data[0] & 0x1f; // AND 00011111 + memcpy(&pkt->psfb.media_src, data + 8, 4); + pkt->psfb.media_src = be32toh(pkt->psfb.media_src); + pkt->psfb.fci = data + 12; + pkt->psfb.fci_len = size - 12; + } + else if (pkt->packet_type == RTCP_PACKET_XR && size >= 24) // 207, see RFC 3611, however we can handle only 1 block + { + pkt->xr.block_type = data[8]; + pkt->xr.block_specific = data[9]; + memcpy(&pkt->xr.block_len, data + 10, 2); + pkt->xr.block_len = 4 * be16toh(pkt->xr.block_len); + if (pkt->xr.block_type != 4 || pkt->xr.block_len != 8) + return 0; // We can only parse handle Receiver Reference Time Report with 8 byte NTP timestamp + + memcpy(&pkt->xr.ntp.sec, data + 12, 4); + pkt->xr.ntp.sec = be32toh(pkt->xr.ntp.sec); + memcpy(&pkt->xr.ntp.frac, data + 16, 4); + pkt->xr.ntp.frac = be32toh(pkt->xr.ntp.frac); + } + else + return -1; // Don't know how to parse + +/* + DPRINTF(E_DBG, L_PLAYER, "RTCP PACKET vers=%d, padding=%d, len=%" PRIu16 ", payload_len=%zu, ssrc=%" PRIu32 "\n", pkt->version, pkt->padding, pkt->len, pkt->payload_len, pkt->ssrc); + if (pkt->packet_type == RTCP_PACKET_APP) + DPRINTF(E_DBG, L_PLAYER, "RTCP APP PACKET subtype=%d, name=%s\n", pkt->app.subtype, pkt->app.name); + else if (pkt->packet_type == RTCP_PACKET_XR) + DPRINTF(E_DBG, L_PLAYER, "RTCP XR PACKET block_type=%d, block_len=%" PRIu16 "\n", pkt->xr.block_type, pkt->xr.block_len); +*/ + + return 0; + + packet_malformed: + DPRINTF(E_SPAM, L_PLAYER, "Ignoring incoming packet, packet is non-RTCP, malformed or partial (size=%zu)\n", size); + return -1; +} diff --git a/src/outputs/rtp_common.h b/src/outputs/rtp_common.h index 233c6c58..23086a78 100644 --- a/src/outputs/rtp_common.h +++ b/src/outputs/rtp_common.h @@ -5,12 +5,30 @@ #include #include +#ifdef HAVE_ENDIAN_H +# include +#elif defined(HAVE_SYS_ENDIAN_H) +# include +#elif defined(HAVE_LIBKERN_OSBYTEORDER_H) +#include +#define htobe16(x) OSSwapHostToBigInt16(x) +#define be16toh(x) OSSwapBigToHostInt16(x) +#define htobe32(x) OSSwapHostToBigInt32(x) +#define be32toh(x) OSSwapBigToHostInt32(x) +#endif + struct rtcp_timestamp { uint32_t pos; struct timespec ts; }; +struct ntp_timestamp +{ + uint32_t sec; + uint32_t frac; +}; + struct rtp_packet { uint16_t seqnum; // Sequence number @@ -27,6 +45,52 @@ struct rtp_packet size_t data_len; // Length of actual packet data }; +struct rtcp_packet +{ + uint8_t version; // Always 2 + bool padding; + uint16_t len; + uint32_t ssrc; + + uint8_t *payload; + size_t payload_len; + + enum rtcp_packet_type + { + RTCP_PACKET_RR = 201, // RFC 3550 + RTCP_PACKET_APP = 204, // RFC 1889 + RTCP_PACKET_PSFB = 206, // RFC 4585 + RTCP_PACKET_XR = 207, // RFC 3611 + } packet_type; + + union + { + struct rtcp_packet_rr + { + uint8_t report_count; + } rr; + struct rtcp_packet_app + { + uint8_t subtype; + char name[5]; // Zero-terminated + } app; + struct rtcp_packet_psfb + { + uint8_t message_type; + uint32_t media_src; + uint8_t *fci; + size_t fci_len; + } psfb; + struct rtcp_packet_xr + { + uint8_t block_type; + uint8_t block_specific; + uint16_t block_len; + struct ntp_timestamp ntp; + } xr; + }; +}; + // An RTP session is characterised by all the receivers belonging to the session // getting the same RTP and RTCP packets. So if you have clients that require // different sample rates or where only some can accept encrypted payloads then @@ -101,4 +165,7 @@ rtp_sync_is_time(struct rtp_session *session); struct rtp_packet * rtp_sync_packet_next(struct rtp_session *session, struct rtcp_timestamp cur_stamp, char type); +int +rtcp_packet_parse(struct rtcp_packet *pkt, uint8_t *data, size_t size); + #endif /* !__RTP_COMMON_H__ */