/** {@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; }
/** * 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; }
/** * 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; }
/** {@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); } }
/** * 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; }
/** * 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; } } } } }
/** * 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(); }
/** 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); }
/** * 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); } }
/** * 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; } } }