Esempio n. 1
0
  /** {@inheritDoc} */
  public long getLocalTime(long ssrc, long rtp0) {
    // don't use getSSRCDesc, because we don't want to create an instance
    SSRCDesc ssrcDesc = ssrcs.get(ssrc);
    if (ssrcDesc == null) {
      return -1;
    }

    // get all required times
    long clockRate; // the clock rate for the RTP clock for the given SSRC
    long rtp1; // some time X in the RTP clock for the given SSRC
    double ntp1; // the same time X in the source's wallclock
    String endpointId;
    synchronized (ssrcDesc) {
      clockRate = ssrcDesc.clockRate;
      rtp1 = ssrcDesc.rtpTime;
      ntp1 = ssrcDesc.ntpTime;
      endpointId = ssrcDesc.endpointId;
    }

    // if something is missing, we can't calculate the time
    if (clockRate == -1 || rtp1 == -1 || ntp1 == -1.0 || endpointId == null) {
      return -1;
    }

    Endpoint endpoint = endpoints.get(ssrcDesc.endpointId);
    if (endpoint == null) {
      return -1;
    }

    double ntp2; // some time Y in the source's wallclock (same clock as for ntp1)
    long local2; // the same time Y in the local clock
    synchronized (endpoint) {
      ntp2 = endpoint.ntpTime;
      local2 = endpoint.localTime;
    }

    if (ntp2 == -1.0 || local2 == -1) {
      return -1;
    }

    // crunch the numbers. we're looking for 'local0',
    // the local time corresponding to 'rtp0'
    long local0;

    double diff1S = ntp1 - ntp2;
    double diff2S = ((double) TimeUtils.rtpDiff(rtp0, rtp1)) / clockRate;

    long diffMs = Math.round((diff1S + diff2S) * 1000);

    local0 = local2 + diffMs;

    return local0;
  }
Esempio n. 2
0
 /**
  * Returns the <tt>SSRCDesc</tt> instance mapped to the SSRC <tt>ssrc</tt>. If no instance is
  * mapped to <tt>ssrc</tt>, create one and inserts it in the map. Always returns non-null.
  *
  * @param ssrc the ssrc to get the <tt>SSRCDesc</tt> for.
  * @return the <tt>SSRCDesc</tt> instance mapped to the SSRC <tt>ssrc</tt>.
  */
 private SSRCDesc getSSRCDesc(long ssrc) {
   SSRCDesc ssrcDesc = ssrcs.get(ssrc);
   if (ssrcDesc == null) {
     synchronized (ssrcs) {
       ssrcDesc = ssrcs.get(ssrc);
       if (ssrcDesc == null) {
         ssrcDesc = new SSRCDesc();
         ssrcs.put(ssrc, ssrcDesc);
       }
     }
   }
   return ssrcDesc;
 }
Esempio n. 3
0
  /**
   * Returns the <tt>Endpoint</tt> with id <tt>endpointId</tt>. Creates an <tt>Endpoint</tt> if
   * necessary. Always returns non-null.
   *
   * @param endpointId the string identifying the endpoint.
   * @return the <tt>Endpoint</tt> with id <tt>endpointId</tt>. Creates an <tt>Endpoint</tt> if
   *     necessary.
   */
  private Endpoint getEndpoint(String endpointId) {
    Endpoint endpoint = endpoints.get(endpointId);
    if (endpoint == null) {
      synchronized (endpoints) {
        endpoint = endpoints.get(endpointId);
        if (endpoint == null) {
          endpoint = new Endpoint();
          endpoints.put(endpointId, endpoint);
        }
      }
    }

    return endpoint;
  }
  /**
   * Returns default <tt>WebRtcDataStream</tt> if it's ready or <tt>null</tt> otherwise.
   *
   * @return <tt>WebRtcDataStream</tt> if it's ready or <tt>null</tt> otherwise.
   * @throws IOException
   */
  public WebRtcDataStream getDefaultDataStream() throws IOException {
    WebRtcDataStream def;

    synchronized (this) {
      if (sctpSocket == null) {
        def = null;
      } else {
        // Channel that runs on sid 0
        def = channels.get(0);
        if (def == null) {
          def = openChannel(0, 0, 0, 0, "default");
        }
        // Pawel Domas: Must be acknowledged before use
        /*
         * XXX Lyubomir Marinov: We're always sending ordered. According
         * to "WebRTC Data Channel Establishment Protocol", we can start
         * sending messages containing user data after the
         * DATA_CHANNEL_OPEN message has been sent without waiting for
         * the reception of the corresponding DATA_CHANNEL_ACK message.
         */
        //                if (!def.isAcknowledged())
        //                    def = null;
      }
    }
    return def;
  }
Esempio n. 5
0
  /** {@inheritDoc} */
  public synchronized Result createConference(String creator, String mucRoomName) {
    Conference conference = conferenceMap.get(mucRoomName);
    if (conference == null) {
      // Create new conference
      try {
        ApiResult result = api.createNewConference(creator, mucRoomName);

        if (result.getError() == null) {
          conference = result.getConference();
          conferenceMap.put(mucRoomName, conference);
        } else if (result.getStatusCode() == 409 && result.getError().getConflictId() != null) {
          Number conflictId = result.getError().getConflictId();

          // Conference already exists(check if we have it locally)
          conference = findConferenceForId(conflictId);

          logger.info("Conference '" + mucRoomName + "' already " + "allocated, id: " + conflictId);

          // do GET conflict conference
          if (conference == null) {
            ApiResult getResult = api.getConference(conflictId);
            if (getResult.getConference() != null) {
              conference = getResult.getConference();
              // Fill full room name as it is not transferred
              // over REST API
              conference.setMucRoomName(mucRoomName);

              conferenceMap.put(mucRoomName, conference);
            } else {
              logger.error("API error: " + result);
              return new Result(RESULT_INTERNAL_ERROR, result.getError().getMessage());
            }
          }
        } else {
          // Other error
          logger.error("API error: " + result);
          return new Result(RESULT_INTERNAL_ERROR, result.getError().getMessage());
        }
      } catch (FaultTolerantRESTRequest.RetryExhaustedException e) {
        logger.error(e, e);
        return new Result(RESULT_INTERNAL_ERROR, e.getMessage());
      } catch (UnsupportedEncodingException e) {
        logger.error(e, e);
        return new Result(RESULT_INTERNAL_ERROR, e.getMessage());
      }
    }

    // Verify owner == creator
    if (creator.equals(conference.getOwner())) {
      return new Result(RESULT_OK);
    } else {
      logger.error(
          "Room " + mucRoomName + ", conflict : " + creator + " != " + conference.getOwner());

      return new Result(RESULT_CONFLICT);
    }
  }
Esempio n. 6
0
  /**
   * Returns {@link JitsiMeetConference} for given MUC {@code roomName} or {@code null} if no
   * conference has been allocated yet.
   *
   * @param roomName the name of MUC room for which we want get the {@code JitsiMeetConference}
   *     instance.
   * @return the {@code JitsiMeetConference} for the specified {@code roomName} or {@code null} if
   *     no conference has been allocated yet
   */
  public JitsiMeetConference getConference(String roomName) {
    roomName = roomName.toLowerCase();

    // Other public methods which read from and/or write to the field
    // conferences are sychronized (e.g. conferenceEnded, conferenceRequest)
    // so synchronization is necessary here as well.
    synchronized (this) {
      return conferences.get(roomName);
    }
  }
  /**
   * 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;
  }
  /**
   * 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;
  }
Esempio n. 9
0
 /**
  * Removes the RTP-NTP mapping for a given SSRC.
  *
  * @param ssrc the SSRC for which to remove the RTP-NTP mapping
  */
 void removeMapping(long ssrc) {
   if (ssrcs.containsKey(ssrc)) {
     synchronized (ssrcs) {
       SSRCDesc ssrcDesc = ssrcs.get(ssrc);
       if (ssrcDesc != null) {
         synchronized (ssrcDesc) {
           ssrcDesc.ntpTime = -1.0;
           ssrcDesc.rtpTime = -1;
         }
       }
     }
   }
 }
Esempio n. 10
0
  /**
   * Allocates new focus for given MUC room.
   *
   * @param room the name of MUC room for which new conference has to be allocated.
   * @param properties configuration properties map included in the request.
   * @return <tt>true</tt> if conference focus is in the room and ready to handle session
   *     participants.
   * @throws Exception if for any reason we have failed to create the conference
   */
  public synchronized boolean conferenceRequest(String room, Map<String, String> properties)
      throws Exception {
    if (StringUtils.isNullOrEmpty(room)) return false;

    if (shutdownInProgress && !conferences.containsKey(room)) return false;

    if (!conferences.containsKey(room)) {
      createConference(room, properties);
    }

    JitsiMeetConference conference = conferences.get(room);

    return conference.isInTheRoom();
  }
Esempio n. 11
0
  /** Deletes conference for given <tt>mucRoomName</tt> through the API. */
  Result deleteConference(String mucRoomName) {
    Conference conference = conferenceMap.get(mucRoomName);
    if (conference != null) {
      // Delete conference
      Number id = conference.getId();

      int result = deleteConference(id);

      if (result == RESULT_OK) {
        conferenceMap.remove(mucRoomName);
      } else {
        // Other error
        return new Result(result);
      }
    }
    return new Result(RESULT_OK);
  }
Esempio n. 12
0
  /**
   * Implements in order to listen for ended conferences and remove them from the reservation
   * system.
   *
   * <p>{@inheritDoc}
   */
  @Override
  public synchronized void onFocusDestroyed(String roomName) {
    // roomName = MucUtil.extractName(roomName);

    // Focus destroyed
    Conference conference = conferenceMap.get(roomName);
    if (conference == null) {
      logger.info("Conference " + roomName + " already destroyed");
      return;
    }

    Result result = deleteConference(roomName);
    if (result.getCode() == RESULT_OK) {
      logger.info("Deleted conference from the reservation system: " + roomName);
    } else {
      logger.error("Failed to delete room: " + roomName + ", error code: " + result);
    }
  }
  /**
   * {@inheritDoc}
   *
   * <p>SCTP input data callback.
   */
  @Override
  public void onSctpPacket(
      byte[] data, int sid, int ssn, int tsn, long ppid, int context, int flags) {
    if (ppid == WEB_RTC_PPID_CTRL) {
      // Channel control PPID
      try {
        onCtrlPacket(data, sid);
      } catch (IOException e) {
        logger.error("IOException when processing ctrl packet", e);
      }
    } else if (ppid == WEB_RTC_PPID_STRING || ppid == WEB_RTC_PPID_BIN) {
      WebRtcDataStream channel;

      synchronized (this) {
        channel = channels.get(sid);
      }

      if (channel == null) {
        logger.error("No channel found for sid: " + sid);
        return;
      }
      if (ppid == WEB_RTC_PPID_STRING) {
        // WebRTC String
        String str;
        String charsetName = "UTF-8";

        try {
          str = new String(data, charsetName);
        } catch (UnsupportedEncodingException uee) {
          logger.error("Unsupported charset encoding/name " + charsetName, uee);
          str = null;
        }
        channel.onStringMsg(str);
      } else {
        // WebRTC Binary
        channel.onBinaryMsg(data);
      }
    } else {
      logger.warn("Got message on unsupported PPID: " + ppid);
    }
  }
    /**
     * 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);
    }
  /**
   * Handles control packet.
   *
   * @param data raw packet data that arrived on control PPID.
   * @param sid SCTP stream id on which the data has arrived.
   */
  private synchronized void onCtrlPacket(byte[] data, int sid) throws IOException {
    ByteBuffer buffer = ByteBuffer.wrap(data);
    int messageType = /* 1 byte unsigned integer */ 0xFF & buffer.get();

    if (messageType == MSG_CHANNEL_ACK) {
      if (logger.isDebugEnabled()) {
        logger.debug(getEndpoint().getID() + " ACK received SID: " + sid);
      }
      // Open channel ACK
      WebRtcDataStream channel = channels.get(sid);
      if (channel != null) {
        // Ack check prevents from firing multiple notifications
        // if we get more than one ACKs (by mistake/bug).
        if (!channel.isAcknowledged()) {
          channel.ackReceived();
          notifyChannelOpened(channel);
        } else {
          logger.warn("Redundant ACK received for SID: " + sid);
        }
      } else {
        logger.error("No channel exists on sid: " + sid);
      }
    } else if (messageType == MSG_OPEN_CHANNEL) {
      int channelType = /* 1 byte unsigned integer */ 0xFF & buffer.get();
      int priority = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort();
      long reliability = /* 4 bytes unsigned integer */ 0xFFFFFFFFL & buffer.getInt();
      int labelLength = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort();
      int protocolLength = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort();
      String label;
      String protocol;

      if (labelLength == 0) {
        label = "";
      } else {
        byte[] labelBytes = new byte[labelLength];

        buffer.get(labelBytes);
        label = new String(labelBytes, "UTF-8");
      }
      if (protocolLength == 0) {
        protocol = "";
      } else {
        byte[] protocolBytes = new byte[protocolLength];

        buffer.get(protocolBytes);
        protocol = new String(protocolBytes, "UTF-8");
      }

      if (logger.isDebugEnabled()) {
        logger.debug(
            "!!! "
                + getEndpoint().getID()
                + " data channel open request on SID: "
                + sid
                + " type: "
                + channelType
                + " prio: "
                + priority
                + " reliab: "
                + reliability
                + " label: "
                + label
                + " proto: "
                + protocol);
      }

      if (channels.containsKey(sid)) {
        logger.error("Channel on sid: " + sid + " already exists");
      }

      WebRtcDataStream newChannel = new WebRtcDataStream(sctpSocket, sid, label, true);
      channels.put(sid, newChannel);

      sendOpenChannelAck(sid);

      notifyChannelOpened(newChannel);
    } else {
      logger.error("Unexpected ctrl msg type: " + messageType);
    }
  }
Esempio n. 16
0
 /**
  * Returns {@link JitsiMeetConference} for given MUC <tt>roomName</tt> or <tt>null</tt> if no
  * conference has been allocated yet.
  *
  * @param roomName the name of MUC room for which we want get the {@link JitsiMeetConference}
  *     instance.
  */
 public JitsiMeetConference getConference(String roomName) {
   return conferences.get(roomName);
 }
    /**
     * 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;
        }
      }
    }