From 7c08000d4feaba3e7caa3ca71d7b549e624ebd09 Mon Sep 17 00:00:00 2001
From: Lyubomir Marinov <lyubomir.marinov@jitsi.org>
Date: Fri, 3 Aug 2012 15:14:19 +0000
Subject: [PATCH] - Works on removing the video of a video conference
 participant from display on the remaining non-focus participants. - Tries to
 fix a crash in PortAudio (often seen at the end of video conference calls).

---
 .../jitsi/impl/neomedia/MediaStreamImpl.java  | 19 ++++-
 .../impl/neomedia/RTPTranslatorImpl.java      | 84 ++++++++++++++++---
 .../protocol/portaudio/PortAudioStream.java   | 46 ++++++----
 3 files changed, 121 insertions(+), 28 deletions(-)

diff --git a/src/org/jitsi/impl/neomedia/MediaStreamImpl.java b/src/org/jitsi/impl/neomedia/MediaStreamImpl.java
index 6a975309..feef7edb 100644
--- a/src/org/jitsi/impl/neomedia/MediaStreamImpl.java
+++ b/src/org/jitsi/impl/neomedia/MediaStreamImpl.java
@@ -1879,7 +1879,22 @@ private void stop(MediaDirection direction)
                 && (MediaDirection.SENDRECV.equals(startedDirection)
                         || MediaDirection.SENDONLY.equals(startedDirection)))
         {
-            stopSendStreams(false);
+            /*
+             * XXX It is not very clear at the time of this writing whether the
+             * SendStreams are to be stopped or closed. On one hand, stopping a
+             * direction may be a temporary transition which relies on retaining
+             * the SSRC. On the other hand, it may be permanent. In which case,
+             * the respective ReveiveStream on the remote peer will timeout at
+             * some point in time. In the context of video conferences, when a
+             * member stops the streaming of their video without leaving the
+             * conference, they will stop their SendStreams. However, the other
+             * members will need respective BYE RTCP packets in order to know
+             * that they are to remove the associated ReceiveStreams from
+             * display. The initial version of the code here used to stop the
+             * SendStreams without closing them but, given the considerations
+             * above, it is being changed to close them in the case of video.
+             */
+            stopSendStreams(this instanceof VideoMediaStream);
 
             if (deviceSession != null)
                 deviceSession.stop(MediaDirection.SENDONLY);
@@ -1951,9 +1966,11 @@ private void stopReceiveStreams()
                 try
                 {
                     if (logger.isTraceEnabled())
+                    {
                         logger.trace(
                                 "Stopping receive stream with hashcode "
                                     + receiveStream.hashCode());
+                    }
 
                     DataSource receiveStreamDataSource
                         = receiveStream.getDataSource();
diff --git a/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java b/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java
index d03ef4ef..b7490c4b 100644
--- a/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java
+++ b/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java
@@ -339,6 +339,55 @@ public synchronized void initialize(
         }
     }
 
+    /**
+     * Logs information about an RTCP packet using {@link #logger} for debugging
+     * purposes.
+     *
+     * @param obj the object which is the source of the log request
+     * @param methodName the name of the method on <tt>obj</tt> which is the
+     * source of the log request
+     * @param buffer the <tt>byte</tt>s which (possibly) represent an RTCP
+     * packet to be logged for debugging purposes
+     * @param offset the position within <tt>buffer</tt> at which the valid data
+     * begins
+     * @param length the number of bytes in <tt>buffer</tt> which constitute the
+     * valid data 
+     */
+    private static void logRTCP(
+            Object obj, String methodName,
+            byte[] buffer, int offset, int length)
+    {
+        if (length > 8)
+        {
+            byte b0 = buffer[offset];
+            int v = (b0 & 0xc0) >>> 6;
+
+            if (v == 2)
+            {
+                byte b1 = buffer[offset + 1];
+                int pt = b1 & 0xff;
+
+                if (pt == 203 /* BYE */)
+                {
+                    int sc = b0 & 0x1f;
+                    long ssrc = (sc > 0) ? readInt(buffer, offset + 4) : -1;
+
+                    logger.trace(
+                            obj.getClass().getName()
+                                + '.'
+                                + methodName
+                                + ": RTCP BYE v="
+                                + v
+                                + "; pt="
+                                + pt
+                                + "; ssrc="
+                                + ssrc
+                                + ';');
+                }
+            }
+        }
+    }
+
     private int read(
             PushSourceStreamDesc streamDesc,
             byte[] buffer, int offset, int length,
@@ -372,6 +421,8 @@ private int read(
                 format = streamRTPManagerDesc.getFormat(payloadType);
             }
         }
+        else if (logger.isTraceEnabled())
+            logRTCP(this, "read", buffer, offset, read);
 
         OutputDataStreamImpl outputStream
             = data
@@ -560,23 +611,32 @@ private synchronized int doWrite(
 
                 if (streamRTPManagerDesc != exclusion)
                 {
-                    if (data && (format != null) && (length > 0))
+                    if (data)
                     {
-                        Integer payloadType
-                            = streamRTPManagerDesc.getPayloadType(format);
-
-                        if ((payloadType == null) && (exclusion != null))
-                            payloadType = exclusion.getPayloadType(format);
-                        if (payloadType != null)
+                        if ((format != null) && (length > 0))
                         {
-                            int payloadTypeByteIndex = offset + 1;
+                            Integer payloadType
+                                = streamRTPManagerDesc.getPayloadType(format);
 
-                            buffer[payloadTypeByteIndex]
-                                = (byte)
-                                    ((buffer[payloadTypeByteIndex] & 0x80)
-                                        | (payloadType & 0x7f));
+                            if ((payloadType == null) && (exclusion != null))
+                                payloadType = exclusion.getPayloadType(format);
+                            if (payloadType != null)
+                            {
+                                int payloadTypeByteIndex = offset + 1;
+
+                                buffer[payloadTypeByteIndex]
+                                    = (byte)
+                                        ((buffer[payloadTypeByteIndex] & 0x80)
+                                            | (payloadType & 0x7f));
+                            }
                         }
                     }
+                    else if (logger.isTraceEnabled())
+                    {
+                        logRTCP(
+                                this, "doWrite",
+                                buffer, offset, length);
+                    }
 
                     int streamWrite
                         = streamDesc.stream.write(buffer, offset, length);
diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/portaudio/PortAudioStream.java b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/portaudio/PortAudioStream.java
index e840d3a8..c0bb159b 100644
--- a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/portaudio/PortAudioStream.java
+++ b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/portaudio/PortAudioStream.java
@@ -242,6 +242,12 @@ synchronized void setDeviceIndex(int deviceIndex)
         // DataSource#disconnect
         if (this.deviceIndex != PortAudio.paNoDevice)
         {
+            /*
+             * Just to be on the safe side, make sure #read(Buffer) is not
+             * currently executing.
+             */
+            waitWhileStreamIsBusy();
+
             if (stream != 0)
             {
                 try
@@ -420,21 +426,7 @@ public synchronized void start()
     public synchronized void stop()
         throws IOException
     {
-        boolean interrupted = false;
-
-        while (streamIsBusy)
-        {
-            try
-            {
-                wait();
-            }
-            catch (InterruptedException iex)
-            {
-                interrupted = true;
-            }
-        }
-        if (interrupted)
-            Thread.currentThread().interrupt();
+        waitWhileStreamIsBusy();
 
         try
         {
@@ -450,4 +442,28 @@ public synchronized void stop()
             throw ioex;
         }
     }
+
+    /**
+     * Waits on this instance while {@link #streamIsBusy} is equal to
+     * <tt>true</tt> i.e. until it becomes <tt>false</tt>. The method should
+     * only be called by a thread that is the owner of this object's monitor.
+     */
+    private void waitWhileStreamIsBusy()
+    {
+        boolean interrupted = false;
+
+        while ((stream != 0) && streamIsBusy)
+        {
+            try
+            {
+                wait();
+            }
+            catch (InterruptedException iex)
+            {
+                interrupted = true;
+            }
+        }
+        if (interrupted)
+            Thread.currentThread().interrupt();
+    }
 }
-- 
GitLab