Volume control implementation for media service.

cusax-fix
Damian Minkov 16 years ago
parent afd68625d4
commit d82a4be4b0

@ -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;
}

@ -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 <tt>Logger</tt> used by the <tt>VolumeControlImpl</tt> 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 <tt>VolumeChangeListener</tt> interested in volume change events
* through VolumeControl Interface.
*/
private List<VolumeChangeListener> volumeChangeListeners =
new ArrayList<VolumeChangeListener>();
/**
* Listeners interested in volume change inside jmf.
*/
private List<GainChangeListener> 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 <code>GainControl</code> 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 <code>GainChangeEvent</code> is posted when the state
* of the <code>GainControl</code> changes.
*
* @param listener The object to deliver events to.
*/
public void addGainChangeListener(GainChangeListener listener)
{
if(listener != null)
{
if(gainChangeListeners == null)
gainChangeListeners = new ArrayList<GainChangeListener>();
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 <tt>VolumeChangeListener</tt> 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 <tt>VolumeChangeListener</tt>.
*
* @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<VolumeChangeListener> copyVolumeListeners;
synchronized(volumeChangeListeners)
{
copyVolumeListeners =
new ArrayList<VolumeChangeListener>(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;
}
}

@ -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 <tt>PortAudioRenderer</tt> instance.
*/
public PortAudioRenderer()
{
this(true);
}
/**
* Initializes a new <tt>PortAudioRenderer</tt> 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 <tt>Object</tt>s which represent the controls
* available for the owner of this instance
*/
public Object[] getControls()
{
return new Object[]{gainControl};
}
}

@ -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)

Loading…
Cancel
Save