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)