From: Adrien CLERC Date: Wed, 20 May 2015 13:15:06 +0000 (+0200) Subject: Add adapted RTCP code for 4.0 release X-Git-Tag: v4.2.1~2393 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c47070a1098b43672d3cea9bf946bae1f822a912;p=thirdparty%2Ftvheadend.git Add adapted RTCP code for 4.0 release --- diff --git a/Makefile b/Makefile index 16ba80421..b34aeebd3 100644 --- a/Makefile +++ b/Makefile @@ -303,6 +303,7 @@ SRCS-${CONFIG_IPTV} += \ src/input/mpegts/iptv/iptv_http.c \ src/input/mpegts/iptv/iptv_udp.c \ src/input/mpegts/iptv/iptv_rtsp.c \ + src/input/mpegts/iptv/iptv_rtcp.c \ src/input/mpegts/iptv/iptv_pipe.c # TSfile diff --git a/src/input/mpegts/iptv/iptv_rtcp.c b/src/input/mpegts/iptv/iptv_rtcp.c new file mode 100644 index 000000000..360812288 --- /dev/null +++ b/src/input/mpegts/iptv/iptv_rtcp.c @@ -0,0 +1,375 @@ +/* + * Multicasted IPTV Input + * Copyright (C) 2012 Adrien CLERC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "iptv_rtcp.h" + +#include +#include +#include +#include +#include +#include + +#include "iptv_private.h" + +#ifdef UNDEF +/* + Part of RFC 3350. + Used to send SDES data + */ +static char * +rtp_write_sdes(char *b, u_int32_t src, int argc, + rtcp_sdes_type_t type[], char *value[], + int length[]) +{ + rtcp_sdes_t *s = (rtcp_sdes_t *) b; + rtcp_sdes_item_t *rsp; + int i; + int len; + int pad; + + /* SSRC header */ + s->src = src; + rsp = &s->item[0]; + + /* SDES items */ + for (i = 0; i < argc; i++) + { + rsp->type = type[i]; + len = length[i]; + if (len > RTP_MAX_SDES) + { + /* invalid length, may want to take other action */ + len = RTP_MAX_SDES; + } + rsp->length = len; + memcpy(rsp->data, value[i], len); + rsp = (rtcp_sdes_item_t *) & rsp->data[len]; + } + + /* terminate with end marker and pad to next 4-octet boundary */ + len = ((char *) rsp) - b; + pad = 4 - (len & 0x3); + b = (char *) rsp; + while (pad--) *b++ = RTCP_SDES_END; + + return b; +} +#endif + +/* + * From RFC3350 + * tp: the last time an RTCP packet was transmitted; + + tc: the current time; + + tn: the next scheduled transmission time of an RTCP packet; + + members: the most current estimate for the number of session + members; + + senders: the most current estimate for the number of senders in + the session; + + rtcp_bw: The target RTCP bandwidth, i.e., the total bandwidth + that will be used for RTCP packets by all members of this session, + in octets per second. This will be a specified fraction of the + "session bandwidth" parameter supplied to the application at + startup. + + we_sent: Flag that is true if the application has sent data + since the 2nd previous RTCP report was transmitted. + + avg_rtcp_size: The average compound RTCP packet size, in octets, + over all RTCP packets sent and received by this participant. The + size includes lower-layer transport and network protocol headers + (e.g., UDP and IP) as explained in Section 6.2. + + initial: Flag that is true if the application has not yet sent + an RTCP packet. + */ +static double +rtcp_interval(int members, int senders, double rtcp_bw, int we_sent, double avg_rtcp_size, int initial) +{ + /* + * Minimum average time between RTCP packets from this site (in + * seconds). This time prevents the reports from `clumping' when + * sessions are small and the law of large numbers isn't helping + * to smooth out the traffic. It also keeps the report interval + * from becoming ridiculously small during transient outages like + * a network partition. + */ + double const RTCP_MIN_TIME = 5.; + + /* + * Fraction of the RTCP bandwidth to be shared among active + * senders. (This fraction was chosen so that in a typical + * session with one or two active senders, the computed report + * time would be roughly equal to the minimum report time so that + * we don't unnecessarily slow down receiver reports.) The + * receiver fraction must be 1 - the sender fraction. + */ + double const RTCP_SENDER_BW_FRACTION = 0.25; + double const RTCP_RCVR_BW_FRACTION = (1 - RTCP_SENDER_BW_FRACTION); + + /* To compensate for "timer reconsideration" converging to a + * value below the intended average. + */ + double const COMPENSATION = 2.71828 - 1.5; + + double t; /* interval */ + double rtcp_min_time = RTCP_MIN_TIME; + int n; /* no. of members for computation */ + + /* + * Very first call at application start-up uses half the min + * delay for quicker notification while still allowing some time + * before reporting for randomization and to learn about other + * sources so the report interval will converge to the correct + * interval more quickly. + */ + if (initial) + { + rtcp_min_time /= 2; + } + /* + * Dedicate a fraction of the RTCP bandwidth to senders unless + * the number of senders is large enough that their share is + * more than that fraction. + */ + n = members; + if (senders <= members * RTCP_SENDER_BW_FRACTION) + { + if (we_sent) + { + rtcp_bw *= RTCP_SENDER_BW_FRACTION; + n = senders; + } else + { + rtcp_bw *= RTCP_RCVR_BW_FRACTION; + n -= senders; + } + } + + /* + * The effective number of sites times the average packet size is + * the total number of octets sent when each site sends a report. + * Dividing this by the effective bandwidth gives the time + * interval over which those packets must be sent in order to + * meet the bandwidth target, with a minimum enforced. In that + * time interval we send one report so this time is also our + * average time between reports. + */ + t = avg_rtcp_size * n / rtcp_bw; + if (t < rtcp_min_time) t = rtcp_min_time; + + /* + * To avoid traffic bursts from unintended synchronization with + * other sites, we then pick our actual next report interval as a + * random number uniformly distributed between 0.5*t and 1.5*t. + */ + t = t * (drand48() + 0.5); + t = t / COMPENSATION; + return t; +} + +/* + Append RTCP header data to the buffer. + Version and padding are set to fixed values, i.e. 2 and 0; + */ +static void +rtcp_append_headers(sbuf_t *buffer, rtcp_t *packet) +{ + packet->common.version = 2; + packet->common.p = 0; + + uint8_t byte = 0; + byte |= packet->common.version << 6; + byte |= packet->common.p << 5; + byte |= packet->common.count; + sbuf_put_byte(buffer, byte); + byte = packet->common.pt; + sbuf_put_byte(buffer, byte); + sbuf_append(buffer, &packet->common.length, sizeof(packet->common.length)); +} + +/* + Append RTCP receiver report data to the buffer. + */ +static void +rtcp_append_rr(sbuf_t *buffer, rtcp_t *packet) +{ + uint8_t byte = 0; + rtcp_rr_t report = packet->r.rr.rr[0]; + + sbuf_append(buffer, &packet->r.rr.ssrc, sizeof(packet->r.rr.ssrc)); + sbuf_append(buffer, &report.ssrc, sizeof(report.ssrc)); + byte = report.fraction; + sbuf_put_byte(buffer, byte); + + // Set the lost number in 3 times + byte = (report.lost & 0xff0000) >> 16; + sbuf_put_byte(buffer, byte); + byte = (report.lost & 0x00ff00) >> 8; + sbuf_put_byte(buffer, byte); + byte = report.lost & 0x0000ff; + sbuf_put_byte(buffer, byte); + + sbuf_append(buffer, &report.last_seq, sizeof(report.last_seq)); + sbuf_append(buffer, &report.jitter, sizeof(report.jitter)); + sbuf_append(buffer, &report.lsr, sizeof(report.lsr)); + sbuf_append(buffer, &report.dlsr, sizeof(report.dlsr)); +} + +/* + Just send the buffer to the host in the rtcp_info. + */ +static void +rtcp_send(iptv_rtcp_info_t *info, sbuf_t *buffer) +{ + // We don't care of the result right now + udp_write(info->connection, buffer->sb_data, buffer->sb_ptr, NULL); +} + +/* + Send a complete receiver report (RR). + It uses the actual informations stored in rtcp_info. + */ +static void +rtcp_send_rr(iptv_rtcp_info_t *info) +{ + rtcp_rr_t report; + + report.ssrc = htonl(info->source_ssrc); + + // Fill in the extended last sequence + union { + uint16_t buffer[2]; + uint32_t result; + } join2; + join2.buffer[0] = htons(info->sequence_cycle); + join2.buffer[1] = htons(info->last_received_sequence); + report.last_seq = join2.result; + + // We don't compute this for now + report.fraction = 0; + report.lost = -1; + report.lsr = htonl(0); + report.dlsr = htonl(0); + + // TODO: see how to put something meaningful + report.jitter = htonl(12); + + // Build the full packet + rtcp_t packet; + packet.common.pt = RTCP_RR; + packet.common.count = 1; + // TODO : set the real length + packet.common.length = htons(7); + packet.r.rr.ssrc = htonl(info->my_ssrc); + packet.r.rr.rr[0] = report; + + // Build the network packet + sbuf_t network_buffer; + sbuf_init(&network_buffer); + rtcp_append_headers(&network_buffer, &packet); + rtcp_append_rr(&network_buffer, &packet); + + // Send it + rtcp_send(info, &network_buffer); + + // TODO : send also the SDES CNAME packet + + // Cleanup + sbuf_free(&network_buffer); +} + +int +rtcp_init(iptv_rtcp_info_t * info) + +{ + info->last_ts = 0; + info->next_ts = 0; + info->members = 2; + info->senders = 1; + info->last_received_sequence = 0; + info->sequence_cycle = 1; + info->source_ssrc = 0; + info->average_packet_size = 52; + + // Fill my SSRC + // TODO: have a better random + unsigned int seed = 21 * time(NULL); + seed += 37 * clock(); + seed += 97 * getpid(); + srandom(seed); + info->my_ssrc = random(); + + return 0; +} + +int +rtcp_destroy(iptv_rtcp_info_t *info) +{ + return 0; +} + +/* + * Buffer is a raw RTP buffer + */ +int +rtcp_receiver_update(iptv_rtcp_info_t *info, uint8_t *buffer) +{ + union { + uint8_t bytes[2]; + uint16_t n; + } join2; + join2.bytes[0] = buffer[2]; + join2.bytes[1] = buffer[3]; + int new_sequence = ntohs(join2.n); + + if(new_sequence < info->last_received_sequence) + { + ++info->sequence_cycle; + } + info->last_received_sequence = new_sequence; + + union { + uint8_t bytes[4]; + uint32_t n; + } join4; + join4.bytes[0] = buffer[8]; + join4.bytes[1] = buffer[9]; + join4.bytes[2] = buffer[10]; + join4.bytes[3] = buffer[11]; + info->source_ssrc = ntohl(join4.n); + + time_t current_time = time(NULL); + if(info->next_ts < current_time) + { + // Re-send + rtcp_send_rr(info); + + // Re-schedule + double interval = rtcp_interval(info->members, info->senders, 12, 0, info->average_packet_size, info->last_ts == 0); + info->next_ts = current_time + interval; + info->last_ts = current_time; + } + + return 0; +} diff --git a/src/input/mpegts/iptv/iptv_rtcp.h b/src/input/mpegts/iptv/iptv_rtcp.h new file mode 100644 index 000000000..8070518ef --- /dev/null +++ b/src/input/mpegts/iptv/iptv_rtcp.h @@ -0,0 +1,185 @@ +/* + * Tvheadend + * Copyright (C) 2012 Adrien CLERC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RTCP_H +#define RTCP_H + +#include "iptv_private.h" +#include "udp.h" + +/* + * Current protocol version. + */ +#define RTP_VERSION 2 + +#define RTP_SEQ_MOD (1<<16) +#define RTP_MAX_SDES 255 /* maximum text length for SDES */ + +typedef enum +{ + RTCP_SR = 200, + RTCP_RR = 201, + RTCP_SDES = 202, + RTCP_BYE = 203, + RTCP_APP = 204 +} rtcp_type_t; + +typedef enum +{ + RTCP_SDES_END = 0, + RTCP_SDES_CNAME = 1, + RTCP_SDES_NAME = 2, + RTCP_SDES_EMAIL = 3, + RTCP_SDES_PHONE = 4, + RTCP_SDES_LOC = 5, + RTCP_SDES_TOOL = 6, + RTCP_SDES_NOTE = 7, + RTCP_SDES_PRIV = 8 +} rtcp_sdes_type_t; + +/* + * RTCP common header word + */ +typedef struct +{ + unsigned int version : 2; /* protocol version */ + unsigned int p : 1; /* padding flag */ + unsigned int count : 5; /* varies by packet type */ + unsigned int pt : 8; /* RTCP packet type */ + uint16_t length; /* pkt len in words, w/o this word */ +} rtcp_common_t; + + +/* + * Big-endian mask for version, padding bit and packet type pair + */ +#define RTCP_VALID_MASK (0xc000 | 0x2000 | 0xfe) +#define RTCP_VALID_VALUE ((RTP_VERSION << 14) | RTCP_SR) + +/* + * Reception report block + */ +typedef struct +{ + uint32_t ssrc; /* data source being reported */ + unsigned int fraction : 8; /* fraction lost since last SR/RR */ + + int lost : 24; /* cumul. no. pkts lost (signed!) */ + uint32_t last_seq; /* extended last seq. no. received */ + uint32_t jitter; /* interarrival jitter */ + uint32_t lsr; /* last SR packet from this source */ + uint32_t dlsr; /* delay since last SR packet */ +} rtcp_rr_t; + +/* + * SDES item + */ +typedef struct +{ + u_int8_t type; /* type of item (rtcp_sdes_type_t) */ + u_int8_t length; /* length of item (in octets) */ + char data[1]; /* text, not null-terminated */ +} rtcp_sdes_item_t; + +typedef struct rtcp_sdes +{ + uint32_t src; /* first SSRC/CSRC */ + rtcp_sdes_item_t item[1]; /* list of SDES items */ +} rtcp_sdes_t; + +/* + * One RTCP packet + */ +typedef struct +{ + rtcp_common_t common; /* common header */ + + union + { + + /* sender report (SR) */ + struct + { + uint32_t ssrc; /* sender generating this report */ + uint32_t ntp_sec; /* NTP timestamp */ + uint32_t ntp_frac; + uint32_t rtp_ts; /* RTP timestamp */ + uint32_t psent; /* packets sent */ + uint32_t osent; /* octets sent */ + rtcp_rr_t rr[1]; /* variable-length list */ + } sr; + + /* reception report (RR) */ + struct + { + uint32_t ssrc; /* receiver generating this report */ + rtcp_rr_t rr[1]; /* variable-length list */ + } rr; + + /* source description (SDES) */ + rtcp_sdes_t sdes; + + /* BYE */ + struct + { + uint32_t src[1]; /* list of sources */ + /* can't express trailing text for reason */ + } bye; + } r; +} rtcp_t; + +typedef struct iptv_rtcp_info { + /* Last transmitted packet timestamp */ + time_t last_ts; + /* Next scheduled packet sending timestamp */ + time_t next_ts; + + double average_packet_size; + + int members; + int senders; + + uint16_t last_received_sequence; + uint16_t sequence_cycle; + + /* Connection to the RTCP remote. Initialized by the RTSP code. */ + udp_connection_t *connection; + + uint32_t source_ssrc; + uint32_t my_ssrc; +} iptv_rtcp_info_t; + +/* + Init rtcp_info field of the rtsp_info. + Return 0 if everything was OK. + rtcp_destroy must be called when rtsp_info is destroyed. + */ +int rtcp_init(iptv_rtcp_info_t *info); + +/* + Destroy rtcp_info field of rtsp_info. + */ +int rtcp_destroy(iptv_rtcp_info_t *info); + +/* + Update RTCP informations. + It can also send a RTCP RR packet if the timer has expired. + */ +int rtcp_receiver_update(iptv_rtcp_info_t *info, uint8_t *rtp_packet); +#endif /* IPTV_RTCP_H */ +