diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java
index 41867b450..1ddc6c69b 100644
--- a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java
+++ b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java
@@ -542,7 +542,9 @@ public ZrtpControl createZrtpControl()
*/
public VolumeControl getVolumeControl()
{
- // returns the uninitialized value as still not implemented
+ if(volumeControl == null)
+ volumeControl = new VolumeControlImpl();
+
return volumeControl;
}
diff --git a/src/net/java/sip/communicator/impl/neomedia/VolumeControlImpl.java b/src/net/java/sip/communicator/impl/neomedia/VolumeControlImpl.java
new file mode 100644
index 000000000..0ed67a73d
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/neomedia/VolumeControlImpl.java
@@ -0,0 +1,402 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.neomedia;
+
+import net.java.sip.communicator.service.neomedia.*;
+import net.java.sip.communicator.service.neomedia.event.*;
+import net.java.sip.communicator.util.*;
+
+import javax.media.*;
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * Controls media service playback volume. If a volume level is set
+ * we change it on all current players, as we synchronize volume on all players.
+ * Implements interface exposed from media service, also implements
+ * the interface used in the Renderer part of JMF and merges the two
+ * functionalities to work as one.
+ *
+ * @author Damian Minkov
+ */
+public class VolumeControlImpl
+ implements VolumeControl,
+ GainControl
+{
+ /**
+ * The Logger used by the VolumeControlImpl class and
+ * its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(VolumeControlImpl.class);
+
+ /**
+ * The minimum volume level we can handle.
+ */
+ private static final float MIN_VOLUME_LEVEL = 0.0F;
+
+ /**
+ * The maximum volume level we can handle.
+ */
+ private static final float MAX_VOLUME_LEVEL = 1.0F;
+
+ /**
+ * The default volume level.
+ */
+ private static final float DEFAULT_VOLUME_LEVEL = 0.5F;
+
+ /**
+ * The VolumeChangeListener interested in volume change events
+ * through VolumeControl Interface.
+ */
+ private List volumeChangeListeners =
+ new ArrayList();
+
+ /**
+ * Listeners interested in volume change inside jmf.
+ */
+ private List gainChangeListeners;
+
+ /**
+ * The current volume level.
+ */
+ private float currentVolumeLevel = DEFAULT_VOLUME_LEVEL;
+
+ /**
+ * Current mute state, by default we start unmuted.
+ */
+ private boolean currentMuteState = false;
+
+ /**
+ * Current playback level in db.
+ */
+ private float db;
+
+ /**
+ * The initial volume level, when this instance was created.
+ */
+ private float initialVolumeLevel = DEFAULT_VOLUME_LEVEL;
+
+ /**
+ * Creates volume control instance and initialise initial level value
+ * if stored in config service.
+ */
+ VolumeControlImpl()
+ {
+ // read initial level from config service if any
+ String initialPlaybackLevel =
+ NeomediaActivator.getConfigurationService()
+ .getString(PLAYBACK_VOLUME_LEVEL_PROPERTY_NAME);
+ try
+ {
+ if(initialPlaybackLevel != null)
+ {
+ currentVolumeLevel = Float.valueOf(initialPlaybackLevel);
+ initialVolumeLevel = currentVolumeLevel;
+
+ if(logger.isDebugEnabled())
+ logger.debug("Restore playback volume: "
+ + currentVolumeLevel);
+ }
+ }
+ catch(Throwable t)
+ {
+ logger.warn("Error restoring playback volume", t);
+ }
+ }
+
+ /**
+ * Current volume value.
+ *
+ * @return the current volume level.
+ *
+ * @see net.java.sip.communicator.service.neomedia.VolumeControl
+ */
+ public float getVolume()
+ {
+ return currentVolumeLevel;
+ }
+
+ /**
+ * Get the current gain set for this
+ * object as a value between 0.0 and 1.0
+ *
+ * @return The gain in the level scale (0.0-1.0).
+ *
+ * @see javax.media.GainControl
+ */
+ public float getLevel()
+ {
+ return this.currentVolumeLevel;
+ }
+
+ /**
+ * Returns the minimum allowed volume value.
+ *
+ * @return the minimum allowed volume value.
+ *
+ * @see net.java.sip.communicator.service.neomedia.VolumeControl
+ */
+ public float getMinValue()
+ {
+ return MIN_VOLUME_LEVEL;
+ }
+
+ /**
+ * Returns the maximum allowed volume value.
+ *
+ * @return the maximum allowed volume value.
+ *
+ * @see net.java.sip.communicator.service.neomedia.VolumeControl
+ */
+ public float getMaxValue()
+ {
+ return MAX_VOLUME_LEVEL;
+ }
+
+ /**
+ * Changes volume level.
+ *
+ * @param value the new level to set.
+ * @return the actual level which was set.
+ *
+ * @see net.java.sip.communicator.service.neomedia.VolumeControl
+ */
+ public float setVolume(float value)
+ {
+ return this.setVolumeLevel(value);
+ }
+
+ /**
+ * Set the gain using a floating point scale
+ * with values between 0.0 and 1.0.
+ * 0.0 is silence; 1.0 is the loudest
+ * useful level that this GainControl supports.
+ *
+ * @param level The new gain value specified in the level scale.
+ * @return The level that was actually set.
+ *
+ * @see javax.media.GainControl
+ */
+ public float setLevel(float level)
+ {
+ return this.setVolumeLevel(level);
+ }
+
+ /**
+ * Internal implementation combining setting level from JMF
+ * and from outside Media Service.
+ *
+ * @param value the new value, changed if different from current
+ * volume settings.
+ * @return the value that was changed or just the current one if
+ * the same.
+ */
+ private float setVolumeLevel(float value)
+ {
+ if(this.currentVolumeLevel == value)
+ return value;
+
+ if(value < MIN_VOLUME_LEVEL)
+ this.currentVolumeLevel = MIN_VOLUME_LEVEL;
+ else if(value > MAX_VOLUME_LEVEL)
+ this.currentVolumeLevel = MAX_VOLUME_LEVEL;
+ else
+ this.currentVolumeLevel = value;
+
+ fireVolumeChange();
+
+ float f1 = value / initialVolumeLevel;
+ db = (float)((Math.log((double)f1 != 0.0D ?
+ f1
+ : 0.0001D) / Math.log(10D)) * 20D);
+
+ fireGainEvents();
+
+ return this.currentVolumeLevel;
+ }
+
+ /**
+ * Mutes current sound playback.
+ *
+ * @param mute mutes/unmutes playback.
+ */
+ public void setMute(boolean mute)
+ {
+ if(mute == this.currentMuteState)
+ return;
+
+ this.currentMuteState = mute;
+
+ fireVolumeChange();
+
+ fireGainEvents();
+ }
+
+ /**
+ * Get mute state of sound playback.
+ *
+ * @return mute state of sound playback.
+ */
+ public boolean getMute()
+ {
+ return this.currentMuteState;
+ }
+
+ /**
+ * Set the gain in decibels.
+ * Setting the gain to 0.0 (the default) implies that the audio
+ * signal is neither amplified nor attenuated.
+ * Positive values amplify the audio signal and negative values attenuate
+ * the signal.
+ *
+ * @param gain The new gain in dB.
+ * @return The gain that was actually set.
+ *
+ * @see javax.media.GainControl
+ */
+ public float setDB(float gain)
+ {
+ if(this.db != gain)
+ {
+ this.db = gain;
+ float f1 = (float)Math.pow(10D, (double)this.db / 20D);
+ this.currentVolumeLevel = f1 * this.initialVolumeLevel;
+ if((double)this.currentVolumeLevel < 0.0D)
+ {
+ setVolumeLevel(0.0F);
+ }
+ else if((double)this.currentVolumeLevel > 1.0D)
+ {
+ setVolumeLevel(1.0F);
+ }
+ else
+ {
+ setVolumeLevel(this.currentVolumeLevel);
+ }
+ }
+ return this.db;
+ }
+
+ /**
+ * Get the current gain set for this object in dB.
+ * @return The gain in dB.
+ */
+ public float getDB()
+ {
+ return this.db;
+ }
+
+ /**
+ * Register for gain change update events.
+ * A GainChangeEvent is posted when the state
+ * of the GainControl changes.
+ *
+ * @param listener The object to deliver events to.
+ */
+ public void addGainChangeListener(GainChangeListener listener)
+ {
+ if(listener != null)
+ {
+ if(gainChangeListeners == null)
+ gainChangeListeners = new ArrayList();
+
+ gainChangeListeners.add(listener);
+ }
+ }
+
+ /**
+ * Remove interest in gain change update events.
+ *
+ * @param listener The object that has been receiving events.
+ */
+ public void removeGainChangeListener(GainChangeListener listener)
+ {
+ if(listener != null && gainChangeListeners != null)
+ gainChangeListeners.remove(listener);
+ }
+
+ /**
+ * Adds a VolumeChangeListener to be informed for any change
+ * in the volume levels.
+ *
+ * @param listener volume change listener.
+ */
+ public void addVolumeChangeListener(VolumeChangeListener listener)
+ {
+ synchronized(volumeChangeListeners)
+ {
+ if(!volumeChangeListeners.contains(listener))
+ {
+ volumeChangeListeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Removes a VolumeChangeListener.
+ *
+ * @param listener the volume change listener to be removed.
+ */
+ public void removeVolumeChangeListener(VolumeChangeListener listener)
+ {
+ synchronized(volumeChangeListeners)
+ {
+ volumeChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Fire a change in volume to interested listeners.
+ */
+ private void fireVolumeChange()
+ {
+ List copyVolumeListeners;
+ synchronized(volumeChangeListeners)
+ {
+ copyVolumeListeners =
+ new ArrayList(volumeChangeListeners);
+ }
+
+ VolumeChangeEvent changeEvent = new VolumeChangeEvent(
+ this, this.currentVolumeLevel, this.currentMuteState);
+
+
+ for(VolumeChangeListener l : copyVolumeListeners)
+ {
+ l.volumeChange(changeEvent);
+ }
+ }
+
+ /**
+ * Fire events informing for our current state.
+ */
+ private void fireGainEvents()
+ {
+ if(gainChangeListeners != null)
+ {
+ GainChangeEvent gainchangeevent =
+ new GainChangeEvent(
+ this, currentMuteState, db, currentVolumeLevel);
+
+ for(GainChangeListener gainchangelistener : gainChangeListeners)
+ {
+ gainchangelistener.gainChange(gainchangeevent);
+ }
+ }
+ }
+
+ /**
+ * Not used.
+ * @return
+ */
+ public Component getControlComponent()
+ {
+ return null;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/audio/PortAudioRenderer.java b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/audio/PortAudioRenderer.java
index 3129d85e9..1108d1fab 100644
--- a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/audio/PortAudioRenderer.java
+++ b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/audio/PortAudioRenderer.java
@@ -11,6 +11,7 @@
import javax.media.*;
import javax.media.format.*;
+import net.java.sip.communicator.impl.neomedia.*;
import net.java.sip.communicator.impl.neomedia.control.*;
import net.java.sip.communicator.impl.neomedia.jmfext.media.protocol.portaudio.*;
import net.java.sip.communicator.impl.neomedia.portaudio.*;
@@ -150,11 +151,28 @@ public class PortAudioRenderer
*/
private Format[] supportedInputFormats;
+ /**
+ * Volume Control used to control volume/gain of current played media.
+ */
+ private GainControl gainControl = null;
+
/**
* Initializes a new PortAudioRenderer instance.
*/
public PortAudioRenderer()
{
+ this(true);
+ }
+
+ /**
+ * Initializes a new PortAudioRenderer instance.
+ * @param enableVolumeControl whether we enable volume control or not.
+ */
+ public PortAudioRenderer(boolean enableVolumeControl)
+ {
+ if(enableVolumeControl)
+ this.gainControl = (GainControl)NeomediaActivator
+ .getMediaServiceImpl().getVolumeControl();
}
/**
@@ -484,6 +502,33 @@ private void process(byte[] buffer, int offset, int length)
if (numberOfWrites > 0)
{
+ // if we have some volume setting apply them
+ if(gainControl != null)
+ {
+ if(gainControl.getMute())
+ {
+ Arrays.fill(buffer, (byte)0);
+ }
+ else if(gainControl.getDB() != 0)
+ {
+ // increase/decrease a little more than
+ // if using levels for factor
+ // we use factor = pow(10, dB/10),
+ // but level = pow(10, dB/20);
+ double factor = Math.pow(10, (gainControl.getDB() / 10d));
+
+ for (int i = 0; i < buffer.length; i+=2)
+ {
+ short s = (short)((buffer[i]&0xff)
+ | (buffer[i + 1]<<8));
+ s = (short)(s*factor);
+
+ buffer[i] = (byte) s;
+ buffer[i+1] = (byte) (s >> 8);
+ }
+ }
+ }
+
PortAudio
.Pa_WriteStream(
stream,
@@ -646,4 +691,17 @@ public synchronized void stop()
}
}
}
+
+ /**
+ * Implements {@link javax.media.Controls#getControls()}. Gets the controls
+ * available for the owner of this instance. The current implementation
+ * returns an empty array because it has no available controls.
+ *
+ * @return an array of Objects which represent the controls
+ * available for the owner of this instance
+ */
+ public Object[] getControls()
+ {
+ return new Object[]{gainControl};
+ }
}
diff --git a/src/net/java/sip/communicator/impl/neomedia/notify/PortAudioClipImpl.java b/src/net/java/sip/communicator/impl/neomedia/notify/PortAudioClipImpl.java
index c42968f90..960fad10f 100644
--- a/src/net/java/sip/communicator/impl/neomedia/notify/PortAudioClipImpl.java
+++ b/src/net/java/sip/communicator/impl/neomedia/notify/PortAudioClipImpl.java
@@ -122,7 +122,8 @@ private void runInPlayThread()
{
Buffer buffer = new Buffer();
byte[] bufferData = new byte[1024];
- PortAudioRenderer renderer = new PortAudioRenderer();
+ // don't enable volume control for notifications
+ PortAudioRenderer renderer = new PortAudioRenderer(false);
buffer.setData(bufferData);
while (started)