Skip to content

Commit

Permalink
Detects incoming media dropped on sip side. (jitsi#180)
Browse files Browse the repository at this point in the history
* Detects incoming media dropped on sip side.

If we detect dropped media, print messages, add stats and when it
resumes stat is updated and message is printed that RTP resumed.
The setting is a threshold of time after which we consider media
for dropped, -1 disables the behaviour, default is 10 seconds and
enabled. There is also an option that, when enabled, calls can be
dropped when media stops.

* Fixes comments.

* Fixes comments.
  • Loading branch information
damencho authored and bgrozev committed Jun 5, 2019
1 parent ff4ead6 commit 8f9780d
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 11 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>libjitsi</artifactId>
<version>1.0-20190412.160239-384</version>
<version>1.0-20190605.073004-385</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/jitsi/jigasi/AbstractGatewaySession.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public abstract class AbstractGatewaySession
*/
private int participantsCount = 0;

/**
* Whether media had stopped being received from the gateway side.
*/
protected boolean gatewayMediaDropped = false;

/**
* Creates new <tt>AbstractGatewaySession</tt> that can be used to
* join a conference by using the {@link #createOutgoingCall()} method.
Expand Down Expand Up @@ -371,4 +376,14 @@ void notifyConferenceMemberLeft(ConferenceMember conferenceMember)
{
// we don't have anything to do here
}

/**
* Returns whether current gateway is up and running and media is being
* received or it was dropped.
* @return current gateway media status.
*/
public boolean isGatewayMediaDropped()
{
return gatewayMediaDropped;
}
}
195 changes: 185 additions & 10 deletions src/main/java/org/jitsi/jigasi/SipGatewaySession.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.media.*;
import net.java.sip.communicator.util.Logger;
import org.jitsi.impl.neomedia.*;
import org.jitsi.jigasi.stats.*;
import org.jitsi.jigasi.util.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.utils.concurrent.*;
import org.jitsi.xmpp.extensions.jibri.*;
import org.jitsi.utils.*;
import org.jivesoftware.smack.packet.*;

import java.io.*;
import java.text.*;
import java.util.*;

Expand Down Expand Up @@ -103,6 +106,56 @@ public class SipGatewaySession
*/
private static final String INIT_STATUS_NAME = "Initializing Call";

/**
* The name of the property that is used to enable detection of
* incoming sip RTP drop. Specifying the time with no media that
* we consider that the call had gone bad and we log an error or hang it up.
*/
private static final String P_NAME_MEDIA_DROPPED_THRESHOLD_MS
= "org.jitsi.jigasi.SIP_MEDIA_DROPPED_THRESHOLD_MS";

/**
* By default we consider sip call bad if there is no RTP for 10 seconds.
*/
private static final int DEFAULT_MEDIA_DROPPED_THRESHOLD = 10*1000;

/**
* The name of the property that is used to indicate whether we will hangup
* sip calls with no RTP after some timeout.
*/
private static final String P_NAME_HANGUP_SIP_ON_MEDIA_DROPPED
= "org.jitsi.jigasi.HANGUP_SIP_ON_MEDIA_DROPPED";

/**
* The threshold configured for detecting dropped media.
*/
private static final int mediaDroppedThresholdMs
= JigasiBundleActivator.getConfigurationService().getInt(
P_NAME_MEDIA_DROPPED_THRESHOLD_MS,
DEFAULT_MEDIA_DROPPED_THRESHOLD);

/**
* The executor which periodically calls {@link ExpireMediaStream}.
*/
private static final RecurringRunnableExecutor EXECUTOR
= new RecurringRunnableExecutor(ExpireMediaStream.class.getName());

/**
* The sound file to use when recording is ON.
*/
private static final String REC_ON_SOUND = "sounds/RecordingOn.opus";

/**
* The sound file to use when recording is OFF.
*/
private static final String REC_OFF_SOUND = "sounds/RecordingStopped.opus";

/**
* The runnable responsible for checking sip call incoming RTP and detecting
* if media stop.
*/
private ExpireMediaStream expireMediaStream;

/**
* The {@link OperationSetJitsiMeetTools} for SIP leg.
*/
Expand Down Expand Up @@ -167,16 +220,6 @@ public class SipGatewaySession
*/
private JibriIq.Status currentRecordingStatus = JibriIq.Status.OFF;

/**
* The sound file to use when recording is ON.
*/
private static final String REC_ON_SOUND = "sounds/RecordingOn.opus";

/**
* The sound file to use when recording is OFF.
*/
private static final String REC_OFF_SOUND = "sounds/RecordingStopped.opus";

/**
* Creates new <tt>SipGatewaySession</tt> for given <tt>callResource</tt>
* and <tt>sipCall</tt>. We already have SIP call instance, so this session
Expand Down Expand Up @@ -557,6 +600,29 @@ void initIncomingCall()
}
call.addCallChangeListener(statsHandler);

if (mediaDroppedThresholdMs != -1)
{
CallPeer peer = call.getCallPeers().next();
if(!addExpireRunnable(peer))
{
peer.addCallPeerListener(new CallPeerAdapter()
{
@Override
public void peerStateChanged(CallPeerChangeEvent evt)
{
CallPeer peer = evt.getSourceCallPeer();
CallPeerState peerState = peer.getState();

if(CallPeerState.CONNECTED.equals(peerState))
{
peer.removeCallPeerListener(this);
addExpireRunnable(peer);
}
}
});
}
}

peerStateListener = new CallPeerListener(call);

if (jvbConference != null)
Expand Down Expand Up @@ -669,6 +735,35 @@ private boolean addSsrcRewriter(CallPeer peer)
return false;
}

/**
* Adds a thread that will be checking the sip call for incoming RTP
* and log or hangup it when we hit the threshold.
* @param peer the call peer.
* @return whether had started the thread.
*/
private boolean addExpireRunnable(CallPeer peer)
{
if (peer instanceof MediaAwareCallPeer)
{
MediaAwareCallPeer peerMedia = (MediaAwareCallPeer) peer;

CallPeerMediaHandler mediaHandler = peerMedia.getMediaHandler();
if (mediaHandler != null)
{
MediaStream stream = mediaHandler.getStream(MediaType.AUDIO);
if (stream != null)
{
expireMediaStream
= new ExpireMediaStream((AudioMediaStreamImpl)stream);
EXECUTOR.registerRecurringRunnable(expireMediaStream);
return true;
}
}
}

return false;
}

@Override
public String getMucDisplayName()
{
Expand Down Expand Up @@ -725,6 +820,83 @@ else if (JibriIq.Status.OFF.equals(status))
currentRecordingStatus = status;
}

/**
* PeriodicRunnable that will check incoming RTP and if needed to hangup.
*/
private class ExpireMediaStream
extends PeriodicRunnable
{
/**
* The stream to check.
*/
private AudioMediaStreamImpl stream;

/**
* Whether we had sent stats for dropped media.
*/
private boolean statsSent = false;

public ExpireMediaStream(AudioMediaStreamImpl stream)
{
// we want to check every 2 seconds for the media state
super(2000, false);
this.stream = stream;
}

@Override
public void run()
{
super.run();

try
{
long lastReceived = stream.getLastInputActivityTime();

if(System.currentTimeMillis() - lastReceived
> mediaDroppedThresholdMs)
{
// we want to log only when we go from not-expired into
// expired state
if (!gatewayMediaDropped)
{
logger.error(
"Stopped receiving RTP for " + getSipCall());

if (!statsSent)
{
Statistics.incrementTotalCallsWithMediaDropped();
statsSent = true;
}
}

gatewayMediaDropped = true;

if (JigasiBundleActivator.getConfigurationService()
.getBoolean(P_NAME_HANGUP_SIP_ON_MEDIA_DROPPED, false))
{
CallManager.hangupCall(getSipCall(),
OperationSetBasicTelephony.HANGUP_REASON_TIMEOUT,
"Stopped receiving media");
}

}
else
{
if (gatewayMediaDropped)
{
logger.info("RTP resumed for " + getSipCall());
}
gatewayMediaDropped = false;
}
}
catch(IOException e)
{
//Should not happen
logger.error("Should not happen exception", e);
}
}
}

class SipCallStateListener
implements CallChangeListener
{
Expand Down Expand Up @@ -770,6 +942,9 @@ else if(call.getCallState() == CallState.CALL_ENDED)
if (peerStateListener != null)
peerStateListener.unregister();

EXECUTOR.deRegisterRecurringRunnable(expireMediaStream);
expireMediaStream = null;

// If we have something to show and we're still in the MUC
// then we display error reason string and leave the room with
// 5 sec delay.
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/org/jitsi/jigasi/stats/Statistics.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.lang.management.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.stream.*;

import javax.servlet.http.*;
Expand Down Expand Up @@ -109,6 +110,14 @@ public class Statistics
public static final String TOTAL_CONFERENCE_SECONDS
= "total_conference_seconds";

/**
* The name of the number of conferences which do not receive media from
* the gateway side.
* {@code Integer}.
*/
public static final String TOTAL_CALLS_WITH_DROPPED_MEDIA
= "total_calls_with_dropped_media";

/**
* Total number of participants since started.
*/
Expand All @@ -119,6 +128,12 @@ public class Statistics
*/
private static int totalConferencesCount = 0;

/**
* Total number of calls with dropped media since started.
*/
private static AtomicLong totalCallsWithMediaDroppedCount
= new AtomicLong();

/**
* Cumulative number of seconds of all conferences.
*/
Expand Down Expand Up @@ -172,6 +187,9 @@ public static synchronized void sendJSON(
stats.put(TOTAL_CONFERENCES, totalConferencesCount);
stats.put(TOTAL_NUMBEROFPARTICIPANTS, totalParticipantsCount);
stats.put(TOTAL_CONFERENCE_SECONDS, cumulativeConferenceSeconds);
stats.put(TOTAL_CALLS_WITH_DROPPED_MEDIA,
totalCallsWithMediaDroppedCount.get());


stats.put(SHUTDOWN_IN_PROGRESS,
JigasiBundleActivator.isShutdownInProgress());
Expand Down Expand Up @@ -264,6 +282,14 @@ public static void addTotalConferencesCount(int value)
totalConferencesCount += value;
}

/**
* Increment the value of total number of calls with dropped media.
*/
public static void incrementTotalCallsWithMediaDropped()
{
totalCallsWithMediaDroppedCount.incrementAndGet();
}

/**
* Adds the value to the number of total conference seconds.
* @param value the value to add to the number of total conference seconds.
Expand Down

0 comments on commit 8f9780d

Please sign in to comment.