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