/**
   * Returns the sequence number to use for a specific RTX packet, which is based on the packet's
   * original sequence number.
   *
   * <p>Because we terminate the RTX format, and with simulcast we might translate RTX packets from
   * multiple SSRCs into the same SSRC, we keep count of the RTX packets (and their sequence
   * numbers) which we sent for each SSRC.
   *
   * @param ssrc the SSRC of the RTX stream for the packet.
   * @param defaultSeq the default sequence number to use in case we don't (yet) have any
   *     information about <tt>ssrc</tt>.
   * @return the sequence number which should be used for the next RTX packet sent using SSRC
   *     <tt>ssrc</tt>.
   */
  private int getNextRtxSequenceNumber(long ssrc, int defaultSeq) {
    Integer seq;
    synchronized (rtxSequenceNumbers) {
      seq = rtxSequenceNumbers.get(ssrc);
      if (seq == null) seq = defaultSeq;
      else seq++;

      rtxSequenceNumbers.put(ssrc, seq);
    }

    return seq;
  }
  /**
   * Returns the sequence number to use for a specific RTX packet, which is based on the packet's
   * original sequence number.
   *
   * <p>Because we terminate the RTX format, and with simulcast we might translate RTX packets from
   * multiple SSRCs into the same SSRC, we keep count of the RTX packets (and their sequence
   * numbers) which we sent for each SSRC.
   *
   * @param ssrc the SSRC of the RTX stream for the packet.
   * @return the sequence number which should be used for the next RTX packet sent using SSRC
   *     <tt>ssrc</tt>.
   */
  private int getNextRtxSequenceNumber(long ssrc) {
    Integer seq;
    synchronized (rtxSequenceNumbers) {
      seq = rtxSequenceNumbers.get(ssrc);
      if (seq == null) seq = new Random().nextInt(0xffff);
      else seq++;

      rtxSequenceNumbers.put(ssrc, seq);
    }

    return seq;
  }
  /**
   * Notifies this instance that the dynamic payload types of the associated {@link MediaStream}
   * have changed.
   */
  public void onDynamicPayloadTypesChanged() {
    rtxPayloadType = -1;
    rtxAssociatedPayloadType = -1;

    MediaStream mediaStream = channel.getStream();

    Map<Byte, MediaFormat> mediaFormatMap = mediaStream.getDynamicRTPPayloadTypes();

    Iterator<Map.Entry<Byte, MediaFormat>> it = mediaFormatMap.entrySet().iterator();

    while (it.hasNext() && rtxPayloadType == -1) {
      Map.Entry<Byte, MediaFormat> entry = it.next();
      MediaFormat format = entry.getValue();
      if (!Constants.RTX.equalsIgnoreCase(format.getEncoding())) {
        continue;
      }

      // XXX(gp) we freak out if multiple codecs with RTX support are
      // present.
      rtxPayloadType = entry.getKey();
      rtxAssociatedPayloadType = Byte.parseByte(format.getFormatParameters().get("apt"));
    }
  }
    /**
     * Estimate the <tt>RemoteClock</tt> of a given RTP stream (identified by its SSRC) at a given
     * time.
     *
     * @param ssrc the SSRC of the RTP stream whose <tt>RemoteClock</tt> we want to estimate.
     * @param time the local time that will be mapped to a remote time.
     * @return An estimation of the <tt>RemoteClock</tt> at time "time".
     */
    public RemoteClock estimate(int ssrc, long time) {
      ReceivedRemoteClock receivedRemoteClock = receivedClocks.get(ssrc);
      if (receivedRemoteClock == null || receivedRemoteClock.getFrequencyHz() == -1) {
        // We can't continue if we don't have NTP and RTP timestamps
        // and/or the original sender frequency, so move to the next
        // one.
        return null;
      }

      long delayMillis = time - receivedRemoteClock.getReceivedTime();

      // Estimate the remote wall clock.
      long remoteTime = receivedRemoteClock.getRemoteClock().getRemoteTime();
      long estimatedRemoteTime = remoteTime + delayMillis;

      // Drift the RTP timestamp.
      int rtpTimestamp =
          receivedRemoteClock.getRemoteClock().getRtpTimestamp()
              + ((int) delayMillis) * (receivedRemoteClock.getFrequencyHz() / 1000);
      return new RemoteClock(estimatedRemoteTime, rtpTimestamp);
    }
    /**
     * Inspect an <tt>RTCPCompoundPacket</tt> and build-up the state for future estimations.
     *
     * @param pkt
     */
    public void apply(RTCPCompoundPacket pkt) {
      if (pkt == null || pkt.packets == null || pkt.packets.length == 0) {
        return;
      }

      for (RTCPPacket rtcpPacket : pkt.packets) {
        switch (rtcpPacket.type) {
          case RTCPPacket.SR:
            RTCPSRPacket srPacket = (RTCPSRPacket) rtcpPacket;

            // The media sender SSRC.
            int ssrc = srPacket.ssrc;

            // Convert 64-bit NTP timestamp to Java standard time.
            // Note that java time (milliseconds) by definition has
            // less precision then NTP time (picoseconds) so
            // converting NTP timestamp to java time and back to NTP
            // timestamp loses precision. For example, Tue, Dec 17
            // 2002 09:07:24.810 EST is represented by a single
            // Java-based time value of f22cd1fc8a, but its NTP
            // equivalent are all values ranging from
            // c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c.

            // Use round-off on fractional part to preserve going to
            // lower precision
            long fraction = Math.round(1000D * srPacket.ntptimestamplsw / 0x100000000L);
            /*
             * If the most significant bit (MSB) on the seconds
             * field is set we use a different time base. The
             * following text is a quote from RFC-2030 (SNTP v4):
             *
             * If bit 0 is set, the UTC time is in the range
             * 1968-2036 and UTC time is reckoned from 0h 0m 0s UTC
             * on 1 January 1900. If bit 0 is not set, the time is
             * in the range 2036-2104 and UTC time is reckoned from
             * 6h 28m 16s UTC on 7 February 2036.
             */
            long msb = srPacket.ntptimestampmsw & 0x80000000L;
            long remoteTime =
                (msb == 0)
                    // use base: 7-Feb-2036 @ 06:28:16 UTC
                    ? msb0baseTime + (srPacket.ntptimestampmsw * 1000) + fraction
                    // use base: 1-Jan-1900 @ 01:00:00 UTC
                    : msb1baseTime + (srPacket.ntptimestampmsw * 1000) + fraction;

            // Estimate the clock rate of the sender.
            int frequencyHz = -1;
            if (receivedClocks.containsKey(ssrc)) {
              // Calculate the clock rate.
              ReceivedRemoteClock oldStats = receivedClocks.get(ssrc);
              RemoteClock oldRemoteClock = oldStats.getRemoteClock();
              frequencyHz =
                  Math.round(
                      (float)
                              (((int) srPacket.rtptimestamp - oldRemoteClock.getRtpTimestamp())
                                  & 0xffffffffl)
                          / (remoteTime - oldRemoteClock.getRemoteTime()));
            }

            // Replace whatever was in there before.
            receivedClocks.put(
                ssrc,
                new ReceivedRemoteClock(
                    ssrc, remoteTime, (int) srPacket.rtptimestamp, frequencyHz));
            break;
          case RTCPPacket.SDES:
            break;
        }
      }
    }