/**
   * Retransmits a packet to {@link #channel}. If the destination supports the RTX format, the
   * packet will be encapsulated in RTX, otherwise, the packet will be retransmitted as-is.
   *
   * @param pkt the packet to retransmit.
   * @param after the {@code TransformEngine} in the chain of {@code TransformEngine}s of the
   *     associated {@code MediaStream} after which the injection of {@code pkt} is to begin
   * @return {@code true} if the packet was successfully retransmitted, {@code false} otherwise.
   */
  public boolean retransmit(RawPacket pkt, TransformEngine after) {
    boolean destinationSupportsRtx = channel.getRtxPayloadType() != -1;
    boolean retransmitPlain;

    if (destinationSupportsRtx) {
      long rtxSsrc = getPairedSsrc(pkt.getSSRC());

      if (rtxSsrc == -1) {
        logger.warn("Cannot find SSRC for RTX, retransmitting plain.");
        retransmitPlain = true;
      } else {
        retransmitPlain = !encapsulateInRtxAndTransmit(pkt, rtxSsrc);
      }
    } else {
      retransmitPlain = true;
    }

    if (retransmitPlain) {
      MediaStream mediaStream = channel.getStream();

      if (mediaStream != null) {
        try {
          mediaStream.injectPacket(pkt, /* data */ true, after);
        } catch (TransmissionFailedException tfe) {
          logger.warn("Failed to retransmit a packet.");
          return false;
        }
      }
    }

    return true;
  }
    /**
     * Updates this <tt>RTPStatsMap</tt> with information it gets from the <tt>RawPacket</tt>.
     *
     * @param pkt the <tt>RawPacket</tt> that is being transmitted.
     */
    public void apply(RawPacket pkt) {
      int ssrc = pkt.getSSRC();
      if (this.containsKey(ssrc)) {
        RTPStatsEntry oldRtpStatsEntry = this.get(ssrc);

        // Replace whatever was in there before. A feature of the two's
        // complement encoding (which is used by Java integers) is that
        // the bitwise results for add, subtract, and multiply are the
        // same if both inputs are interpreted as signed values or both
        // inputs are interpreted as unsigned values. (Other encodings
        // like one's complement and signed magnitude don't have this
        // properly.)
        this.put(
            ssrc,
            new RTPStatsEntry(
                ssrc,
                oldRtpStatsEntry.getBytesSent()
                    + pkt.getLength()
                    - pkt.getHeaderLength()
                    - pkt.getPaddingSize(),
                oldRtpStatsEntry.getPacketsSent() + 1));
      } else {
        // Add a new <tt>RTPStatsEntry</tt> in this map.
        this.put(
            ssrc,
            new RTPStatsEntry(
                ssrc, pkt.getLength() - pkt.getHeaderLength() - pkt.getPaddingSize(), 1));
      }
    }
  /**
   * Handles an RTX packet and returns it.
   *
   * @param pkt the packet to handle.
   * @return the packet
   */
  private RawPacket handleRtxPacket(RawPacket pkt) {
    boolean destinationSupportsRtx = channel.getRtxPayloadType() != -1;
    RawPacket mediaPacket = createMediaPacket(pkt);

    if (mediaPacket != null) {
      RawPacketCache cache = channel.getStream().getPacketCache();
      if (cache != null) {
        cache.cachePacket(mediaPacket);
      }
    }

    if (destinationSupportsRtx) {
      pkt.setSequenceNumber(
          getNextRtxSequenceNumber(pkt.getSSRC() & 0xffffffffL, pkt.getSequenceNumber()));
    } else {
      // If the media packet was not reconstructed, drop the RTX packet
      // (by returning null).
      return mediaPacket;
    }

    return pkt;
  }
  /**
   * Creates a {@code RawPacket} which represents the original packet encapsulated in {@code pkt}
   * using the RTX format.
   *
   * @param pkt the packet from which to extract a media packet.
   * @return the extracted media packet.
   */
  private RawPacket createMediaPacket(RawPacket pkt) {
    RawPacket mediaPacket = null;
    long rtxSsrc = pkt.getSSRC() & 0xffffffffL;

    // We need to know the SSRC paired with rtxSsrc *as seen by the
    // receiver (i.e. this.channel)*. However, we only store SSRCs
    // that endpoints *send* with.
    // We therefore assume that SSRC re-writing has not introduced any
    // new SSRCs and therefor the FID mappings known to the senders
    // also apply to receivers.
    RtpChannel sourceChannel = channel.getContent().findChannelByFidSsrc(rtxSsrc);
    if (sourceChannel != null) {
      long mediaSsrc = sourceChannel.getFidPairedSsrc(rtxSsrc);
      if (mediaSsrc != -1) {
        byte apt = sourceChannel.getRtxAssociatedPayloadType();
        if (apt != -1) {
          mediaPacket = new RawPacket(pkt.getBuffer().clone(), pkt.getOffset(), pkt.getLength());

          // Remove the RTX header by moving the RTP header two bytes
          // right.
          byte[] buf = mediaPacket.getBuffer();
          int off = mediaPacket.getOffset();
          System.arraycopy(buf, off, buf, off + 2, mediaPacket.getHeaderLength());

          mediaPacket.setOffset(off + 2);
          mediaPacket.setLength(pkt.getLength() - 2);

          mediaPacket.setSSRC((int) mediaSsrc);
          mediaPacket.setSequenceNumber(pkt.getOriginalSequenceNumber());
          mediaPacket.setPayloadType(apt);
        }
      }
    }

    return mediaPacket;
  }