]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add adapted RTCP code for 4.0 release
authorAdrien CLERC <adrien.clerc@gls-france.com>
Wed, 20 May 2015 13:15:06 +0000 (15:15 +0200)
committerJaroslav Kysela <perex@perex.cz>
Tue, 2 Jun 2015 13:38:15 +0000 (15:38 +0200)
Makefile
src/input/mpegts/iptv/iptv_rtcp.c [new file with mode: 0644]
src/input/mpegts/iptv/iptv_rtcp.h [new file with mode: 0644]

index 16ba804215f8536657b839e0d84154363b32a5b7..b34aeebd3e68d1d58c7be682a12174d7382bf053 100644 (file)
--- 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 (file)
index 0000000..3608122
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "iptv_rtcp.h"
+
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#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 (file)
index 0000000..8070518
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 */
+