From a363312ec6082aac666bc067184e0a876d6301f3 Mon Sep 17 00:00:00 2001
From: paweldomas <pawel.domas@jitsi.org>
Date: Thu, 12 Jun 2014 14:41:05 +0200
Subject: [PATCH] Adds SilenceAudioSystem which can be used in server type
 technologies.

---
 .../neomedia/device/AudioSilenceSystem.java   |  50 +++
 .../impl/neomedia/device/AudioSystem.java     |   2 +
 .../impl/neomedia/device/DeviceSystem.java    |   1 +
 .../protocol/audiosilence/DataSource.java     | 366 ++++++++++++++++++
 4 files changed, 419 insertions(+)
 create mode 100644 src/org/jitsi/impl/neomedia/device/AudioSilenceSystem.java
 create mode 100644 src/org/jitsi/impl/neomedia/jmfext/media/protocol/audiosilence/DataSource.java

diff --git a/src/org/jitsi/impl/neomedia/device/AudioSilenceSystem.java b/src/org/jitsi/impl/neomedia/device/AudioSilenceSystem.java
new file mode 100644
index 00000000..c7a699e1
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/device/AudioSilenceSystem.java
@@ -0,0 +1,50 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.device;
+
+import org.jitsi.impl.neomedia.jmfext.media.protocol.audiosilence.*;
+
+import javax.media.*;
+import java.util.*;
+
+/**
+ * Audio system used by server side technologies that do not capture any sound.
+ * The device is producing silence.
+ *
+ * @author Pawel Domas
+ */
+public class AudioSilenceSystem
+    extends AudioSystem
+{
+    private static final String LOCATOR_PROTOCOL
+        = LOCATOR_PROTOCOL_AUDIOSILENCE;
+
+    protected AudioSilenceSystem()
+        throws Exception
+    {
+        super(AudioSystem.LOCATOR_PROTOCOL_AUDIOSILENCE);
+    }
+
+    @Override
+    protected void doInitialize()
+        throws Exception
+    {
+        CaptureDeviceInfo2 captureDevice
+            = new CaptureDeviceInfo2(
+                    "AudioSilenceCaptureDevice",
+                    new MediaLocator(LOCATOR_PROTOCOL + ":"),
+                    DataSource.SUPPORTED_FORMATS,
+                    null, null, null);
+
+        List<CaptureDeviceInfo2> captureDevices
+            = new ArrayList<CaptureDeviceInfo2>(1);
+
+        captureDevices.add(captureDevice);
+
+        setCaptureDevices(captureDevices);
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/device/AudioSystem.java b/src/org/jitsi/impl/neomedia/device/AudioSystem.java
index 5f7343c4..327356c2 100644
--- a/src/org/jitsi/impl/neomedia/device/AudioSystem.java
+++ b/src/org/jitsi/impl/neomedia/device/AudioSystem.java
@@ -87,6 +87,8 @@ public enum DataFlow
 
     public static final String LOCATOR_PROTOCOL_AUDIORECORD = "audiorecord";
 
+    public static final String LOCATOR_PROTOCOL_AUDIOSILENCE = "audiosilence";
+
     public static final String LOCATOR_PROTOCOL_JAVASOUND = "javasound";
 
     /**
diff --git a/src/org/jitsi/impl/neomedia/device/DeviceSystem.java b/src/org/jitsi/impl/neomedia/device/DeviceSystem.java
index d940fe58..ddd72f28 100644
--- a/src/org/jitsi/impl/neomedia/device/DeviceSystem.java
+++ b/src/org/jitsi/impl/neomedia/device/DeviceSystem.java
@@ -196,6 +196,7 @@ public static void initializeDeviceSystems(MediaType mediaType)
                     OSUtils.IS_WINDOWS ? ".WASAPISystem" : null,
                     OSUtils.IS_MAC ? ".MacCoreaudioSystem" : null,
                     OSUtils.IS_ANDROID ? null : ".PortAudioSystem",
+                    ".AudioSilenceSystem",
                     ".NoneAudioSystem"
                 };
             break;
diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/audiosilence/DataSource.java b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/audiosilence/DataSource.java
new file mode 100644
index 00000000..0e7fbac1
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/audiosilence/DataSource.java
@@ -0,0 +1,366 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.jmfext.media.protocol.audiosilence;
+
+import org.jitsi.impl.neomedia.codec.*;
+import org.jitsi.impl.neomedia.jmfext.media.protocol.*;
+import org.jitsi.impl.neomedia.jmfext.media.renderer.audio.*;
+import org.jitsi.util.*;
+
+import javax.media.*;
+import javax.media.control.*;
+import javax.media.format.*;
+import javax.media.protocol.*;
+import java.io.*;
+import java.util.*;
+
+/**
+ * Implements a <tt>CaptureDevice</tt> which provides silence in the form of
+ * audio media.
+ *
+ * @author Lyubomir Marinov
+ * @author Pawel Domas
+ */
+public class DataSource
+    extends AbstractPushBufferCaptureDevice
+{
+    /**
+     * The logger.
+     */
+    private final static Logger logger = Logger.getLogger(DataSource.class);
+
+    /**
+     * The compile-time flag which determines whether
+     * <tt>AudioSilenceCaptureDevice</tt> and, more specifically,
+     * <tt>AudioSilenceStream</tt> are to be used by <tt>AudioMixer</tt> for the
+     * mere purposes of ticking the clock which makes <tt>AudioMixer</tt> read
+     * media from its inputs, mix it, and write it to its outputs. The preferred
+     * value is <tt>true</tt> because it causes the <tt>AudioMixer</tt> to not
+     * push media unless at least one <tt>Channel</tt> is receiving actual
+     * media.
+     */
+    private static final boolean CLOCK_ONLY = true;
+
+    /**
+     * The interval of time in milliseconds between two consecutive ticks of the
+     * clock used by <tt>AudioSilenceCaptureDevice</tt> and, more specifically,
+     * <tt>AudioSilenceStream</tt>.
+     */
+    private static final long CLOCK_TICK_INTERVAL = 20;
+
+    /**
+     * The list of <tt>Format</tt>s supported by the
+     * <tt>AudioSilenceCaptureDevice</tt> instances.
+     */
+    public static final Format[] SUPPORTED_FORMATS
+        = new Format[]
+        {
+            new AudioFormat(
+                AudioFormat.LINEAR,
+                48000,
+                16,
+                1,
+                AudioFormat.LITTLE_ENDIAN,
+                AudioFormat.SIGNED,
+                Format.NOT_SPECIFIED,
+                Format.NOT_SPECIFIED,
+                Format.byteArray)
+        };
+
+    /**
+     * {@inheritDoc}
+     *
+     * Implements
+     * {@link AbstractPushBufferCaptureDevice#createStream(int, FormatControl)}.
+     */
+    @Override
+    protected AudioSilenceStream createStream(
+        int streamIndex,
+        FormatControl formatControl)
+    {
+        return new AudioSilenceStream(this, formatControl);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Overrides the super implementation in order to return the list of
+     * <tt>Format</tt>s hardcoded as supported in
+     * <tt>AudioSilenceCaptureDevice</tt> because the super looks them up by
+     * <tt>CaptureDeviceInfo</tt> and this instance does not have one.
+     */
+    @Override
+    protected Format[] getSupportedFormats(int streamIndex)
+    {
+        return SUPPORTED_FORMATS.clone();
+    }
+
+    /**
+     * Implements a <tt>PushBufferStream</tt> which provides silence in the form
+     * of audio media.
+     */
+    private static class AudioSilenceStream
+        extends AbstractPushBufferStream<DataSource>
+        implements Runnable
+    {
+        /**
+         * The indicator which determines whether {@link #start()} has been
+         * invoked on this instance without an intervening {@link #stop()}.
+         */
+        private boolean started;
+
+        /**
+         * The <tt>Thread</tt> which pushes available media data out of this
+         * instance to its consumer i.e. <tt>BufferTransferHandler</tt>.
+         */
+        private Thread thread;
+
+        /**
+         * Initializes a new <tt>AudioSilenceStream</tt> which is to be exposed
+         * by a specific <tt>AudioSilenceCaptureDevice</tt> and which is to have
+         * its <tt>Format</tt>-related information abstracted by a specific
+         * <tt>FormatControl</tt>.
+         *
+         * @param dataSource the <tt>AudioSilenceCaptureDevice</tt> which is
+         * initializing the new instance and which is to expose it in its array
+         * of <tt>PushBufferStream</tt>s
+         * @param formatControl the <tt>FormatControl</tt> which is to abstract
+         * the <tt>Format</tt>-related information of the new instance
+         */
+        public AudioSilenceStream(
+            DataSource dataSource,
+            FormatControl formatControl)
+        {
+            super(dataSource, formatControl);
+        }
+
+        /**
+         * Reads available media data from this instance into a specific
+         * <tt>Buffer</tt>.
+         *
+         * @param buffer the <tt>Buffer</tt> to write the available media data
+         * into
+         * @throws IOException if an I/O error has prevented the reading of
+         * available media data from this instance into the specified
+         * <tt>buffer</tt>
+         */
+        @Override
+        public void read(Buffer buffer)
+            throws IOException
+        {
+            if (CLOCK_ONLY)
+            {
+                buffer.setLength(0);
+            }
+            else
+            {
+                AudioFormat format = (AudioFormat) getFormat();
+                int frameSizeInBytes
+                    = format.getChannels()
+                    * (((int) format.getSampleRate()) / 50)
+                    * (format.getSampleSizeInBits() / 8);
+
+                byte[] data
+                    = AbstractCodec2.validateByteArraySize(
+                    buffer,
+                    frameSizeInBytes,
+                    false);
+
+                Arrays.fill(data, 0, frameSizeInBytes, (byte) 0);
+
+                buffer.setFormat(format);
+                buffer.setLength(frameSizeInBytes);
+                buffer.setOffset(0);
+            }
+        }
+
+        /**
+         * Runs in {@link #thread} and pushes available media data out of this
+         * instance to its consumer i.e. <tt>BufferTransferHandler</tt>.
+         */
+        @Override
+        public void run()
+        {
+            try
+            {
+                /*
+                 * Make sure that the current thread which implements the actual
+                 * ticking of the clock implemented by this instance uses a
+                 * thread priority considered appropriate for audio processing.
+                 */
+                AbstractAudioRenderer.useAudioThreadPriority();
+
+                /*
+                 * The method implements a clock which ticks at a certain and
+                 * regular interval of time which is not affected by the
+                 * duration of the execution of, for example, the invocation of
+                 * BufferTransferHandler.transferData(PushBufferStream).
+                 *
+                 * XXX The implementation utilizes System.currentTimeMillis()
+                 * and, consequently, may be broken by run-time adjustments to
+                 * the system time.
+                 */
+                long tickTime = System.currentTimeMillis();
+
+                while (true)
+                {
+                    long sleepInterval = tickTime - System.currentTimeMillis();
+                    boolean tick = (sleepInterval <= 0);
+
+                    if (tick)
+                    {
+                        /*
+                         * The current thread has woken up just in time or too
+                         * late for the next scheduled clock tick and,
+                         * consequently, the clock should tick right now.
+                         */
+                        tickTime += CLOCK_TICK_INTERVAL;
+                    }
+                    else
+                    {
+                        /*
+                         * The current thread has woken up too early for the
+                         * next scheduled clock tick and, consequently, it
+                         * should sleep until the time of the next scheduled
+                         * clock tick comes.
+                         */
+                        try
+                        {
+                            Thread.sleep(sleepInterval);
+                        }
+                        catch (InterruptedException ie)
+                        {
+                            Thread.currentThread().interrupt();
+                        }
+                        /*
+                         * The clock will not tick and spurious wakeups will be
+                         * handled. However, the current thread will first check
+                         * whether it is still utilized by this
+                         * AudioSilenceStream in order to not delay stop
+                         * requests.
+                         */
+                    }
+
+                    synchronized (this)
+                    {
+                        /*
+                         * If the current Thread is no longer utilized by this
+                         * AudioSilenceStream, it no longer has the right to
+                         * touch it. If this AudioSilenceStream has been
+                         * stopped, the current Thread should stop as well.
+                         */
+                        if ((thread != Thread.currentThread()) || !started)
+                            break;
+                    }
+
+                    if (tick)
+                    {
+                        BufferTransferHandler transferHandler
+                            = this.transferHandler;
+
+                        if (transferHandler != null)
+                        {
+                            try
+                            {
+                                transferHandler.transferData(this);
+                            }
+                            catch (Throwable t)
+                            {
+                                if (t instanceof ThreadDeath)
+                                    throw (ThreadDeath) t;
+                                else
+                                {
+                                    logger.error(t, t);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                synchronized (this)
+                {
+                    if (thread == Thread.currentThread())
+                    {
+                        thread = null;
+                        started = false;
+                        notifyAll();
+                    }
+                }
+            }
+        }
+
+        /**
+         * Starts the transfer of media data from this instance.
+         *
+         * @throws IOException if an error has prevented the start of the
+         * transfer of media from this instance
+         */
+        @Override
+        public synchronized void start()
+            throws IOException
+        {
+            if (thread == null)
+            {
+                String className = getClass().getName();
+
+                thread = new Thread(this, className);
+                thread.setDaemon(true);
+
+                boolean started = false;
+
+                try
+                {
+                    thread.start();
+                    started = true;
+                }
+                finally
+                {
+                    this.started = started;
+                    if (!started)
+                    {
+                        thread = null;
+                        notifyAll();
+
+                        throw new IOException("Failed to start " + className);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Stops the transfer of media data from this instance.
+         *
+         * @throws IOException if an error has prevented the stopping of the
+         * transfer of media from this instance
+         */
+        @Override
+        public synchronized void stop()
+            throws IOException
+        {
+            this.started = false;
+            notifyAll();
+
+            boolean interrupted = false;
+
+            while (thread != null)
+            {
+                try
+                {
+                    wait();
+                }
+                catch (InterruptedException ie)
+                {
+                    interrupted = true;
+                }
+            }
+            if (interrupted)
+                Thread.currentThread().interrupt();
+        }
+    }
+}
-- 
GitLab