From 70faddbcbc2bfa037cb353566b243fd1cf60e86e Mon Sep 17 00:00:00 2001 From: Emil Ivov <emcho@jitsi.org> Date: Thu, 13 Sep 2012 15:40:15 +0000 Subject: [PATCH] Applies a patch from Boris Grozev that allows setting different codec configurations to different accounts. It is now possible to set entirely different codec preference for every account. --- .../jitsi/impl/neomedia/MediaServiceImpl.java | 11 +- src/org/jitsi/impl/neomedia/MediaUtils.java | 2 +- .../codec/EncodingConfigurationImpl.java | 434 +++++++++++++ .../device/AudioMixerMediaDevice.java | 22 + .../impl/neomedia/device/MediaDeviceImpl.java | 46 +- .../device/VideoTranslatorMediaDevice.java | 27 + .../format/MediaFormatFactoryImpl.java | 1 + .../neomedia/MediaConfigurationService.java | 55 ++ .../jitsi/service/neomedia/MediaService.java | 6 + .../neomedia/codec/EncodingConfiguration.java | 607 +++++------------- .../service/neomedia/device/MediaDevice.java | 21 + 11 files changed, 783 insertions(+), 449 deletions(-) create mode 100644 src/org/jitsi/impl/neomedia/codec/EncodingConfigurationImpl.java create mode 100644 src/org/jitsi/service/neomedia/MediaConfigurationService.java rename src/org/jitsi/{impl => service}/neomedia/codec/EncodingConfiguration.java (50%) diff --git a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java index e7dbe022..73acc614 100644 --- a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java +++ b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java @@ -29,6 +29,7 @@ import org.jitsi.service.configuration.*; import org.jitsi.service.libjitsi.*; import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.format.*; import org.jitsi.service.resources.*; @@ -144,7 +145,7 @@ public void propertyChange(PropertyChangeEvent event) * and the order of their preference. */ private final EncodingConfiguration encodingConfiguration - = new EncodingConfiguration(); + = new EncodingConfigurationImpl(); /** * The <tt>MediaFormatFactory</tt> through which <tt>MediaFormat</tt> @@ -1506,4 +1507,12 @@ private static void setupFMJ() } } } + + /** + * Returns a new <tt>EncodingConfiguration</tt> instance. + */ + public EncodingConfiguration getNewEncodingConfiguration() + { + return new EncodingConfigurationImpl(); + } } diff --git a/src/org/jitsi/impl/neomedia/MediaUtils.java b/src/org/jitsi/impl/neomedia/MediaUtils.java index 94846237..d79ced77 100644 --- a/src/org/jitsi/impl/neomedia/MediaUtils.java +++ b/src/org/jitsi/impl/neomedia/MediaUtils.java @@ -148,7 +148,7 @@ public class MediaUtils MediaType.AUDIO, Constants.G722_RTP, 8000); - if (EncodingConfiguration.G729) + if (EncodingConfigurationImpl.G729) { addMediaFormats( (byte) SdpConstants.G729, diff --git a/src/org/jitsi/impl/neomedia/codec/EncodingConfigurationImpl.java b/src/org/jitsi/impl/neomedia/codec/EncodingConfigurationImpl.java new file mode 100644 index 00000000..59035585 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/codec/EncodingConfigurationImpl.java @@ -0,0 +1,434 @@ +/* + * 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.codec; + +import java.io.*; +import java.util.*; + +import javax.media.*; + +import org.jitsi.impl.neomedia.*; +import org.jitsi.impl.neomedia.format.*; +import org.jitsi.service.configuration.*; +import org.jitsi.service.libjitsi.*; +import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.codec.*; +import org.jitsi.service.neomedia.format.*; +import org.jitsi.util.*; + +/** + * Simple configuration of encoding priorities. + * + * @author Damian Minkov + * @author Lyubomir Marinov + * @author Boris Grozev + */ +public class EncodingConfigurationImpl extends EncodingConfiguration +{ + /** + * The SDP preference property. + */ + private static final String PROP_SDP_PREFERENCE + = "net.java.sip.communicator.impl.neomedia.codec.EncodingConfiguration"; + + /** + * The indicator which determines whether the G.729 codec is enabled. + * + * WARNING: The use of G.729 may require a license fee and/or royalty fee in + * some countries and is licensed by + * <a href="http://www.sipro.com">SIPRO Lab Telecom</a>. + */ + public static final boolean G729 = false; + + /** + * The additional custom JMF codecs. + */ + private static final String[] CUSTOM_CODECS = + { + "org.jitsi.impl.neomedia.codec.audio.alaw.DePacketizer", + "org.jitsi.impl.neomedia.codec.audio.alaw.JavaEncoder", + "org.jitsi.impl.neomedia.codec.audio.alaw.Packetizer", + "org.jitsi.impl.neomedia.codec.audio.ulaw.JavaDecoder", + "org.jitsi.impl.neomedia.codec.audio.ulaw.JavaEncoder", + "org.jitsi.impl.neomedia.codec.audio.ulaw.Packetizer", + "org.jitsi.impl.neomedia.codec.audio.speex.JNIDecoder", + "org.jitsi.impl.neomedia.codec.audio.speex.JNIEncoder", + "org.jitsi.impl.neomedia.codec.audio.speex.SpeexResampler", + "org.jitsi.impl.neomedia.codec.audio.speex.JavaDecoder", + "org.jitsi.impl.neomedia.codec.audio.speex.JavaEncoder", + "org.jitsi.impl.neomedia.codec.audio.mp3.JNIEncoder", + "org.jitsi.impl.neomedia.codec.audio.ilbc.JavaDecoder", + "org.jitsi.impl.neomedia.codec.audio.ilbc.JavaEncoder", + G729 + ? "org.jitsi.impl.neomedia.codec.audio.g729.JavaDecoder" + : null, + G729 + ? "org.jitsi.impl.neomedia.codec.audio.g729.JavaEncoder" + : null, + "net.java.sip.communicator.impl.neomedia.codec.audio.g722.JNIDecoder", + "net.java.sip.communicator.impl.neomedia.codec.audio.g722.JNIEncoder", + "org.jitsi.impl.neomedia.codec.audio.gsm.Decoder", + "org.jitsi.impl.neomedia.codec.audio.gsm.Encoder", + "org.jitsi.impl.neomedia.codec.audio.gsm.DePacketizer", + "org.jitsi.impl.neomedia.codec.audio.gsm.Packetizer", + "org.jitsi.impl.neomedia.codec.audio.silk.JavaDecoder", + "org.jitsi.impl.neomedia.codec.audio.silk.JavaEncoder", + "org.jitsi.impl.neomedia.codec.video.h263p.DePacketizer", + "org.jitsi.impl.neomedia.codec.video.h263p.JNIDecoder", + "org.jitsi.impl.neomedia.codec.video.h263p.JNIEncoder", + "org.jitsi.impl.neomedia.codec.video.h263p.Packetizer", + "org.jitsi.impl.neomedia.codec.video.h264.DePacketizer", + "org.jitsi.impl.neomedia.codec.video.h264.JNIDecoder", + "org.jitsi.impl.neomedia.codec.video.h264.JNIEncoder", + "org.jitsi.impl.neomedia.codec.video.h264.Packetizer", + "org.jitsi.impl.neomedia.codec.video.SwScaler" + }; + + /** + * Whether custom codecs have been registered with JFM + */ + private static boolean codecsRegistered = false; + + /** + * Whether custom packages have been registered with JFM + */ + private static boolean packagesRegistered = false; + + /** + * The package prefixes of the additional JMF <tt>DataSource</tt>s (e.g. low + * latency PortAudio and ALSA <tt>CaptureDevice</tt>s). + */ + private static final String[] CUSTOM_PACKAGES + = new String[] + { + "org.jitsi.impl.neomedia.jmfext", + "net.java.sip.communicator.impl.neomedia.jmfext", + "net.sf.fmj" + }; + + /** + * Constructor. Loads the hard-coded default preferences and registers + * packages and codecs with JMF. + */ + public EncodingConfigurationImpl() + { + initializeFormatPreferences(); + + registerCustomPackages(); + registerCustomCodecs(); + } + + /** + * Sets default format preferences. + */ + private void initializeFormatPreferences() + { + // first init default preferences + // video + setEncodingPreference( + "H264", + VideoMediaFormatImpl.DEFAULT_CLOCK_RATE, + 1100); + + setEncodingPreference( + "H263-1998", + VideoMediaFormatImpl.DEFAULT_CLOCK_RATE, + 0); + /* + setEncodingPreference( + "H263", + VideoMediaFormatImpl.DEFAULT_CLOCK_RATE, + 1000); + */ + setEncodingPreference( + "JPEG", + VideoMediaFormatImpl.DEFAULT_CLOCK_RATE, + 950); + setEncodingPreference( + "H261", + VideoMediaFormatImpl.DEFAULT_CLOCK_RATE, + 800); + + // audio + setEncodingPreference("G722", 8000 /* actually, 16 kHz */, 705); + setEncodingPreference("SILK", 24000, 704); + setEncodingPreference("SILK", 16000, 703); + setEncodingPreference("speex", 32000, 701); + setEncodingPreference("speex", 16000, 700); + setEncodingPreference("PCMU", 8000, 650); + setEncodingPreference("PCMA", 8000, 600); + setEncodingPreference("iLBC", 8000, 500); + setEncodingPreference("GSM", 8000, 450); + setEncodingPreference("speex", 8000, 352); + setEncodingPreference("DVI4", 8000, 300); + setEncodingPreference("DVI4", 16000, 250); + setEncodingPreference("G723", 8000, 150); + + setEncodingPreference("SILK", 12000, 0); + setEncodingPreference("SILK", 8000, 0); + setEncodingPreference("G729", 8000, 0 /* proprietary */); + + // enables by default telephone event(DTMF rfc4733), with lowest + // priority as it is not needed to order it with audio codecs + setEncodingPreference(Constants.TELEPHONE_EVENT, 8000, 1); + + } + + /** + * Sets the priority of the given encoding and updates the configuration + * via the configuration service. + * + * @param encoding the <tt>MediaFormat</tt> specifying the encoding to set + * the priority of + * @param priority a positive <tt>int</tt> indicating the priority of + * <tt>encoding</tt> to set + * @param updateConfig Whether configuration should be updated. + */ + @Override + public void setPriority(MediaFormat encoding, int priority, + boolean updateConfig) + { + super.setPriority(encoding, priority); + + if(updateConfig) + { + String encodingEncoding = encoding.getEncoding(); + + // save the settings + LibJitsi.getConfigurationService().setProperty( + PROP_SDP_PREFERENCE + + "." + + getEncodingPreferenceKey(encoding), + priority); + } + } + + /** + * Register in JMF the custom codecs we provide + */ + private void registerCustomCodecs() + { + if(codecsRegistered) + { + return; + } + + // Register the custom codec which haven't already been registered. + @SuppressWarnings("unchecked") + Collection<String> registeredPlugins + = new HashSet<String>( + PlugInManager.getPlugInList( + null, + null, + PlugInManager.CODEC)); + boolean commit = false; + + // Remove JavaRGBToYUV. + PlugInManager.removePlugIn( + "com.sun.media.codec.video.colorspace.JavaRGBToYUV", + PlugInManager.CODEC); + PlugInManager.removePlugIn( + "com.sun.media.codec.video.colorspace.JavaRGBConverter", + PlugInManager.CODEC); + PlugInManager.removePlugIn( + "com.sun.media.codec.video.colorspace.RGBScaler", + PlugInManager.CODEC); + + // Remove JMF's H263 codec. + PlugInManager.removePlugIn( + "com.sun.media.codec.video.vh263.NativeDecoder", + PlugInManager.CODEC); + PlugInManager.removePlugIn( + "com.ibm.media.codec.video.h263.NativeEncoder", + PlugInManager.CODEC); + + // Remove JMF's GSM codec. As working only on some OS. + String gsmCodecPackage = "com.ibm.media.codec.audio.gsm."; + String[] gsmCodecClasses + = new String[] + { + "JavaDecoder", + "JavaDecoder_ms", + "JavaEncoder", + "JavaEncoder_ms", + "NativeDecoder", + "NativeDecoder_ms", + "NativeEncoder", + "NativeEncoder_ms", + "Packetizer" + }; + for(String gsmCodecClass : gsmCodecClasses) + { + PlugInManager.removePlugIn( + gsmCodecPackage + gsmCodecClass, + PlugInManager.CODEC); + } + + /* + * Remove FMJ's JavaSoundCodec because it seems to slow down the + * building of the filter graph and we do not currently seem to need it. + */ + PlugInManager.removePlugIn( + "net.sf.fmj.media.codec.JavaSoundCodec", + PlugInManager.CODEC); + + for (String className : CUSTOM_CODECS) + { + + /* + * A codec with a className of null is configured at compile time to + * not be registered. + */ + if (className == null) + continue; + + if (registeredPlugins.contains(className)) + { + if (logger.isDebugEnabled()) + logger.debug( + "Codec " + className + " is already registered"); + } + else + { + commit = true; + + boolean registered; + Throwable exception = null; + + try + { + Codec codec = (Codec) + Class.forName(className).newInstance(); + + registered = + PlugInManager.addPlugIn( + className, + codec.getSupportedInputFormats(), + codec.getSupportedOutputFormats(null), + PlugInManager.CODEC); + } + catch (Throwable ex) + { + registered = false; + exception = ex; + } + if (registered) + { + if (logger.isDebugEnabled()) + logger.debug( + "Codec " + + className + + " is successfully registered"); + } + else + { + if (logger.isDebugEnabled()) + logger.debug( + "Codec " + + className + + " is NOT succsefully registered", exception); + } + } + } + + /* + * If Jitsi provides a codec which is also provided by FMJ and/or JMF, + * use Jitsi's version. + */ + @SuppressWarnings("unchecked") + Vector<String> codecs + = PlugInManager.getPlugInList(null, null, PlugInManager.CODEC); + + if (codecs != null) + { + boolean setPlugInList = false; + + for (int i = CUSTOM_CODECS.length - 1; i >= 0; i--) + { + String className = CUSTOM_CODECS[i]; + + if (className != null) + { + int classNameIndex = codecs.indexOf(className); + + if (classNameIndex != -1) + { + codecs.remove(classNameIndex); + codecs.add(0, className); + setPlugInList = true; + } + } + } + + if (setPlugInList) + PlugInManager.setPlugInList(codecs, PlugInManager.CODEC); + } + + if (commit && !MediaServiceImpl.isJmfRegistryDisableLoad()) + { + try + { + PlugInManager.commit(); + } + catch (IOException ex) + { + logger.error("Cannot commit to PlugInManager", ex); + } + } + + codecsRegistered = true; + } + + /** + * Register in JMF the custom packages we provide + */ + private void registerCustomPackages() + { + if(packagesRegistered) + { + return; + } + + @SuppressWarnings("unchecked") + Vector<String> packages = PackageManager.getProtocolPrefixList(); + boolean loggerIsDebugEnabled = logger.isDebugEnabled(); + + for (String customPackage : CUSTOM_PACKAGES) + { + /* + * Linear search in a loop but it doesn't have to scale since the + * list is always short. + */ + if (!packages.contains(customPackage)) + { + packages.add(customPackage); + if (loggerIsDebugEnabled) + if (logger.isDebugEnabled()) + logger.debug("Adding package : " + customPackage); + } + } + + PackageManager.setProtocolPrefixList(packages); + PackageManager.commitProtocolPrefixList(); + if (loggerIsDebugEnabled) + { + if (logger.isDebugEnabled()) + logger.debug( + "Registering new protocol prefix list: " + packages); + } + + packagesRegistered = true; + } + + /** + * Loads the "global" preferences. + */ + public void loadConfig() + { + loadFormatPreferencesFromConfig(PROP_SDP_PREFERENCE); + } +} diff --git a/src/org/jitsi/impl/neomedia/device/AudioMixerMediaDevice.java b/src/org/jitsi/impl/neomedia/device/AudioMixerMediaDevice.java index 7ce90c42..95a55fc2 100644 --- a/src/org/jitsi/impl/neomedia/device/AudioMixerMediaDevice.java +++ b/src/org/jitsi/impl/neomedia/device/AudioMixerMediaDevice.java @@ -19,6 +19,7 @@ import org.jitsi.impl.neomedia.conference.*; import org.jitsi.impl.neomedia.protocol.*; import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.event.*; import org.jitsi.service.neomedia.format.*; @@ -443,6 +444,27 @@ public List<MediaFormat> getSupportedFormats( { return device.getSupportedFormats(); } + + /** + * Gets the list of <tt>MediaFormat</tt>s supported by this + * <tt>MediaDevice</tt> and enabled in <tt>encodingConfiguration</tt>. + * + * @param sendPreset not used + * @param receivePreset not used + * @param encodingConfiguration the <tt>EncodingConfiguration</tt> instance + * to use + * @return the list of <tt>MediaFormat</tt>s supported by this + * <tt>MediaDevice</tt> and enabled in <tt>encodingConfiguration</tt>. + * @see MediaDevice#getSupportedFormats(QualityPreset, QualityPreset, + * EncodingConfiguration) + */ + public List<MediaFormat> getSupportedFormats( + QualityPreset sendPreset, + QualityPreset receivePreset, + EncodingConfiguration encodingConfiguration) + { + return device.getSupportedFormats(encodingConfiguration); + } /** * Gets the actual <tt>MediaDevice</tt> which this <tt>MediaDevice</tt> is diff --git a/src/org/jitsi/impl/neomedia/device/MediaDeviceImpl.java b/src/org/jitsi/impl/neomedia/device/MediaDeviceImpl.java index cb79046f..b5a24b4b 100644 --- a/src/org/jitsi/impl/neomedia/device/MediaDeviceImpl.java +++ b/src/org/jitsi/impl/neomedia/device/MediaDeviceImpl.java @@ -21,6 +21,7 @@ import org.jitsi.impl.neomedia.jmfext.media.protocol.*; import org.jitsi.impl.neomedia.protocol.*; import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.format.*; import org.jitsi.util.*; @@ -331,7 +332,8 @@ public MediaType getMediaType() /** * Gets the list of <tt>MediaFormat</tt>s supported by this - * <tt>MediaDevice</tt>. + * <tt>MediaDevice</tt>. Uses the <tt>EncodingConfiguration</tt> from the + * media service, which contains all known encodings. * * @param sendPreset the preset used to set some of the format parameters, * used for video and settings. @@ -343,11 +345,49 @@ public MediaType getMediaType() public List<MediaFormat> getSupportedFormats( QualityPreset sendPreset, QualityPreset receivePreset) + { + return getSupportedFormats(sendPreset, receivePreset, + NeomediaServiceUtils.getMediaServiceImpl() + .getEncodingConfiguration()); + } + + /** + * Gets the list of <tt>MediaFormat</tt>s supported by this + * <tt>MediaDevice</tt> and enabled in <tt>encodingConfiguration</tt>. + * + * @param encodingConfiguration the <tt>EncodingConfiguration</tt> instance + * to use + * @return the list of <tt>MediaFormat</tt>s supported by this device + * and enabled in <tt>encodingConfiguration</tt>. + * @see MediaDevice#getSupportedFormats() + */ + public List<MediaFormat> getSupportedFormats( + EncodingConfiguration encodingConfiguration) + { + return getSupportedFormats(null, null, encodingConfiguration); + } + + /** + * Gets the list of <tt>MediaFormat</tt>s supported by this + * <tt>MediaDevice</tt> and enabled in <tt>encodingConfiguration</tt>. + * + * @param sendPreset the preset used to set some of the format parameters, + * used for video and settings. + * @param receivePreset the preset used to set the receive format + * parameters, used for video and settings. + * @param encodingConfiguration the <tt>EncodingConfiguration</tt> instance + * to use + * @return the list of <tt>MediaFormat</tt>s supported by this device + * and enabled in <tt>encodingConfiguration</tt>. + * @see MediaDevice#getSupportedFormats() + */ + public List<MediaFormat> getSupportedFormats( + QualityPreset sendPreset, + QualityPreset receivePreset, + EncodingConfiguration encodingConfiguration) { MediaServiceImpl mediaServiceImpl = NeomediaServiceUtils.getMediaServiceImpl(); - EncodingConfiguration encodingConfiguration - = mediaServiceImpl.getEncodingConfiguration(); MediaFormat[] supportedEncodings = encodingConfiguration.getSupportedEncodings(getMediaType()); List<MediaFormat> supportedFormats = new ArrayList<MediaFormat>(); diff --git a/src/org/jitsi/impl/neomedia/device/VideoTranslatorMediaDevice.java b/src/org/jitsi/impl/neomedia/device/VideoTranslatorMediaDevice.java index 9757fec4..32816bc7 100644 --- a/src/org/jitsi/impl/neomedia/device/VideoTranslatorMediaDevice.java +++ b/src/org/jitsi/impl/neomedia/device/VideoTranslatorMediaDevice.java @@ -13,6 +13,7 @@ import org.jitsi.impl.neomedia.format.*; import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.format.*; @@ -211,6 +212,32 @@ public List<MediaFormat> getSupportedFormats( return device.getSupportedFormats(localPreset, remotePreset); } + /** + * Returns a list of <tt>MediaFormat</tt> instances representing the media + * formats supported by this <tt>MediaDevice</tt> and enabled in + * <tt>encodingConfiguration</tt>.. + * + * @param localPreset the preset used to set the send format parameters, + * used for video and settings + * @param remotePreset the preset used to set the receive format parameters, + * used for video and settings + * @param encodingConfiguration the <tt>EncodingConfiguration</tt> instance + * to use + * @return the list of <tt>MediaFormat</tt>s supported by this device + * and enabled in <tt>encodingConfiguration</tt>. + * @see MediaDevice#getSupportedFormats(QualityPreset, QualityPreset, + * EncodingConfiguration) + */ + public List<MediaFormat> getSupportedFormats( + QualityPreset localPreset, + QualityPreset remotePreset, + EncodingConfiguration encodingConfiguration) + { + return device.getSupportedFormats(localPreset, + remotePreset, + encodingConfiguration); + } + /** * Gets the actual <tt>MediaDevice</tt> which this <tt>MediaDevice</tt> is * effectively built on top of and forwarding to. diff --git a/src/org/jitsi/impl/neomedia/format/MediaFormatFactoryImpl.java b/src/org/jitsi/impl/neomedia/format/MediaFormatFactoryImpl.java index b2f9f82f..80562319 100644 --- a/src/org/jitsi/impl/neomedia/format/MediaFormatFactoryImpl.java +++ b/src/org/jitsi/impl/neomedia/format/MediaFormatFactoryImpl.java @@ -14,6 +14,7 @@ import org.jitsi.impl.neomedia.*; import org.jitsi.impl.neomedia.codec.*; import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.format.*; import org.jitsi.util.*; diff --git a/src/org/jitsi/service/neomedia/MediaConfigurationService.java b/src/org/jitsi/service/neomedia/MediaConfigurationService.java new file mode 100644 index 00000000..ba6dfc56 --- /dev/null +++ b/src/org/jitsi/service/neomedia/MediaConfigurationService.java @@ -0,0 +1,55 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. See terms of license at gnu.org. + */ +package org.jitsi.service.neomedia; + +import java.awt.*; +import org.jitsi.service.neomedia.codec.*; + +/** + * An interface that exposes the <tt>Component</tt>s used in media + * configuration user interfaces. + * + * @author Boris Grozev + */ +public interface MediaConfigurationService +{ + /** + * Returns a <tt>Component</tt> for audio configuration + * + * @return A <tt>Component</tt> for audio configuration + */ + public Component createAudioConfigPanel(); + + /** + * Returns a <tt>Component</tt> for video configuration + * + * @return A <tt>Component</tt> for video configuration + */ + public Component createVideoConfigPanel(); + + /** + * Returns a <tt>Component</tt> for encodings configuration (either audio or video) + * @param mediaType The type of media -- either MediaType.AUDIO or + * MediaType.VIDEO + * @param encodingConfiguration The <tt>EncodingConfiguration</tt> instance + * to use. If null, creates one on it's own. + * @param autoUpdateConfig Whether we should update the configuration after + * every change. Useful, because in the main audio/video encoding + * configuration we want the changes to apply instantaneously, while in the + * account creation wizards we want to delay the update until the form is + * committed. + * @return The <tt>Component</tt> for encodings configuration + */ + public Component createEncodingControls(MediaType mediaType, + EncodingConfiguration encodingConfiguration, + boolean autoUpdateConfig); + + /** + * Returns a new <tt>EncodingConfiguration</tt> instance + * @return a new <tt>EncodingConfiguration</tt> instance + */ + public EncodingConfiguration getNewEncodingConfiguration(); +} diff --git a/src/org/jitsi/service/neomedia/MediaService.java b/src/org/jitsi/service/neomedia/MediaService.java index 50b37f5b..72aa01e8 100644 --- a/src/org/jitsi/service/neomedia/MediaService.java +++ b/src/org/jitsi/service/neomedia/MediaService.java @@ -9,6 +9,7 @@ import java.beans.*; import java.util.*; +import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.format.*; @@ -317,4 +318,9 @@ public java.awt.Point getOriginForDesktopStreamingDevice( * notified about changes in the values of the properties of this instance */ public void removePropertyChangeListener(PropertyChangeListener listener); + + /** + * Returns a new <tt>EncodingConfiguration</tt> instance. + */ + public EncodingConfiguration getNewEncodingConfiguration(); } diff --git a/src/org/jitsi/impl/neomedia/codec/EncodingConfiguration.java b/src/org/jitsi/service/neomedia/codec/EncodingConfiguration.java similarity index 50% rename from src/org/jitsi/impl/neomedia/codec/EncodingConfiguration.java rename to src/org/jitsi/service/neomedia/codec/EncodingConfiguration.java index 51413b51..bf8fa705 100644 --- a/src/org/jitsi/impl/neomedia/codec/EncodingConfiguration.java +++ b/src/org/jitsi/service/neomedia/codec/EncodingConfiguration.java @@ -4,15 +4,11 @@ * Distributable under LGPL license. * See terms of license at gnu.org. */ -package org.jitsi.impl.neomedia.codec; +package org.jitsi.service.neomedia.codec; -import java.io.*; import java.util.*; -import javax.media.*; - import org.jitsi.impl.neomedia.*; -import org.jitsi.impl.neomedia.format.*; import org.jitsi.service.configuration.*; import org.jitsi.service.libjitsi.*; import org.jitsi.service.neomedia.*; @@ -20,90 +16,20 @@ import org.jitsi.util.*; /** - * Simple configuration of encoding priorities. - * + * Abstract class that manages encoding configurations. It holds information + * about supported formats. + * * @author Damian Minkov * @author Lyubomir Marinov + * @author Boris Grozev */ -public class EncodingConfiguration +public abstract class EncodingConfiguration { - /** * The <tt>Logger</tt> used by this <tt>EncodingConfiguration</tt> instance * for logging output. */ - private final Logger logger = Logger.getLogger(EncodingConfiguration.class); - - /** - * The SDP preference property. - */ - private static final String PROP_SDP_PREFERENCE - = "net.java.sip.communicator.impl.neomedia.codec.EncodingConfiguration"; - - /** - * The indicator which determines whether the G.729 codec is enabled. - * - * WARNING: The use of G.729 may require a license fee and/or royalty fee in - * some countries and is licensed by - * <a href="http://www.sipro.com">SIPRO Lab Telecom</a>. - */ - public static final boolean G729 = false; - - /** - * The additional custom JMF codecs. - */ - private static final String[] CUSTOM_CODECS = - { - "org.jitsi.impl.neomedia.codec.audio.alaw.DePacketizer", - "org.jitsi.impl.neomedia.codec.audio.alaw.JavaEncoder", - "org.jitsi.impl.neomedia.codec.audio.alaw.Packetizer", - "org.jitsi.impl.neomedia.codec.audio.ulaw.JavaDecoder", - "org.jitsi.impl.neomedia.codec.audio.ulaw.JavaEncoder", - "org.jitsi.impl.neomedia.codec.audio.ulaw.Packetizer", - "org.jitsi.impl.neomedia.codec.audio.speex.JNIDecoder", - "org.jitsi.impl.neomedia.codec.audio.speex.JNIEncoder", - "org.jitsi.impl.neomedia.codec.audio.speex.SpeexResampler", - "org.jitsi.impl.neomedia.codec.audio.speex.JavaDecoder", - "org.jitsi.impl.neomedia.codec.audio.speex.JavaEncoder", - "org.jitsi.impl.neomedia.codec.audio.mp3.JNIEncoder", - "org.jitsi.impl.neomedia.codec.audio.ilbc.JavaDecoder", - "org.jitsi.impl.neomedia.codec.audio.ilbc.JavaEncoder", - G729 - ? "org.jitsi.impl.neomedia.codec.audio.g729.JavaDecoder" - : null, - G729 - ? "org.jitsi.impl.neomedia.codec.audio.g729.JavaEncoder" - : null, - "net.java.sip.communicator.impl.neomedia.codec.audio.g722.JNIDecoder", - "net.java.sip.communicator.impl.neomedia.codec.audio.g722.JNIEncoder", - "org.jitsi.impl.neomedia.codec.audio.gsm.Decoder", - "org.jitsi.impl.neomedia.codec.audio.gsm.Encoder", - "org.jitsi.impl.neomedia.codec.audio.gsm.DePacketizer", - "org.jitsi.impl.neomedia.codec.audio.gsm.Packetizer", - "org.jitsi.impl.neomedia.codec.audio.silk.JavaDecoder", - "org.jitsi.impl.neomedia.codec.audio.silk.JavaEncoder", - "org.jitsi.impl.neomedia.codec.video.h263p.DePacketizer", - "org.jitsi.impl.neomedia.codec.video.h263p.JNIDecoder", - "org.jitsi.impl.neomedia.codec.video.h263p.JNIEncoder", - "org.jitsi.impl.neomedia.codec.video.h263p.Packetizer", - "org.jitsi.impl.neomedia.codec.video.h264.DePacketizer", - "org.jitsi.impl.neomedia.codec.video.h264.JNIDecoder", - "org.jitsi.impl.neomedia.codec.video.h264.JNIEncoder", - "org.jitsi.impl.neomedia.codec.video.h264.Packetizer", - "org.jitsi.impl.neomedia.codec.video.SwScaler" - }; - - /** - * The package prefixes of the additional JMF <tt>DataSource</tt>s (e.g. low - * latency PortAudio and ALSA <tt>CaptureDevice</tt>s). - */ - private static final String[] CUSTOM_PACKAGES - = new String[] - { - "org.jitsi.impl.neomedia.jmfext", - "net.java.sip.communicator.impl.neomedia.jmfext", - "net.sf.fmj" - }; + protected final Logger logger = Logger.getLogger(EncodingConfiguration.class); /** * The <tt>Comparator</tt> which sorts the sets according to the settings in @@ -112,6 +38,7 @@ public class EncodingConfiguration private final Comparator<MediaFormat> encodingComparator = new Comparator<MediaFormat>() { + @Override public int compare(MediaFormat s1, MediaFormat s2) { return compareEncodingPreferences(s1, s2); @@ -125,7 +52,7 @@ public int compare(MediaFormat s1, MediaFormat s2) * would be decorelated and other components (such as the UI) should present * them separately. */ - private final Map<String, Integer> encodingPreferences + protected final Map<String, Integer> encodingPreferences = new HashMap<String, Integer>(); /** @@ -140,143 +67,6 @@ public int compare(MediaFormat s1, MediaFormat s2) */ private Set<MediaFormat> supportedVideoEncodings; - /** - * Default constructor. - */ - public EncodingConfiguration() - { - initializeFormatPreferences(); - registerCustomPackages(); - registerCustomCodecs(); - } - - /** - * Retrieves (from the configuration service) preferences specified for - * various formats and assigns default ones to those that haven't been - * mentioned. - */ - private void initializeFormatPreferences() - { - // first init default preferences - // video - setEncodingPreference( - "H264", - VideoMediaFormatImpl.DEFAULT_CLOCK_RATE, - 1100); - - setEncodingPreference( - "H263-1998", - VideoMediaFormatImpl.DEFAULT_CLOCK_RATE, - 0); - /* - setEncodingPreference( - "H263", - VideoMediaFormatImpl.DEFAULT_CLOCK_RATE, - 1000); - */ - setEncodingPreference( - "JPEG", - VideoMediaFormatImpl.DEFAULT_CLOCK_RATE, - 950); - setEncodingPreference( - "H261", - VideoMediaFormatImpl.DEFAULT_CLOCK_RATE, - 800); - - // audio - setEncodingPreference("G722", 8000 /* actually, 16 kHz */, 705); - setEncodingPreference("SILK", 24000, 704); - setEncodingPreference("SILK", 16000, 703); - setEncodingPreference("speex", 32000, 701); - setEncodingPreference("speex", 16000, 700); - setEncodingPreference("PCMU", 8000, 650); - setEncodingPreference("PCMA", 8000, 600); - setEncodingPreference("iLBC", 8000, 500); - setEncodingPreference("GSM", 8000, 450); - setEncodingPreference("speex", 8000, 352); - setEncodingPreference("DVI4", 8000, 300); - setEncodingPreference("DVI4", 16000, 250); - setEncodingPreference("G723", 8000, 150); - - setEncodingPreference("SILK", 12000, 0); - setEncodingPreference("SILK", 8000, 0); - setEncodingPreference("G729", 8000, 0 /* proprietary */); - - // enables by default telephone event(DTMF rfc4733), with lowest - // priority as it is not needed to order it with audio codecs - setEncodingPreference(Constants.TELEPHONE_EVENT, 8000, 1); - - // now override with those that are specified by the user. - ConfigurationService cfg = LibJitsi.getConfigurationService(); - - if (cfg != null) - { - for (String pName - : cfg.getPropertyNamesByPrefix(PROP_SDP_PREFERENCE, false)) - { - String prefStr = cfg.getString(pName); - String fmtName = pName.substring(pName.lastIndexOf('.') + 1); - - // legacy - if (fmtName.contains("sdp")) - { - fmtName = fmtName.replaceAll("sdp", ""); - /* - * If the current version of the property name is also - * associated with a value, ignore the value for the legacy - * one. - */ - if (cfg.getString(PROP_SDP_PREFERENCE + "." + fmtName) - != null) - continue; - } - - int preference = -1; - String encoding; - double clockRate; - - try - { - preference = Integer.parseInt(prefStr); - - int encodingClockRateSeparator = fmtName.lastIndexOf('/'); - - if (encodingClockRateSeparator > -1) - { - encoding - = fmtName.substring(0, encodingClockRateSeparator); - clockRate - = Double.parseDouble( - fmtName.substring( - encodingClockRateSeparator + 1)); - } - else - { - encoding = fmtName; - clockRate = MediaFormatFactory.CLOCK_RATE_NOT_SPECIFIED; - } - } - catch (NumberFormatException nfe) - { - logger.warn( - "Failed to parse format (" - + fmtName - + ") or preference (" - + prefStr - + ").", - nfe); - continue; - } - - setEncodingPreference(encoding, clockRate, preference); - } - } - - // now update the arrays so that they are returned by order of - // preference. - updateSupportedEncodings(); - } - /** * Updates the codecs in the supported sets according to the preferences in * encodingPreferences. If the preference value is <tt>0</tt>, the codec is @@ -302,13 +92,17 @@ private void updateSupportedEncodings() */ private Set<MediaFormat> updateSupportedEncodings(MediaType type) { - Set<MediaFormat> supported + Set<MediaFormat> enabled = new TreeSet<MediaFormat>(encodingComparator); for (MediaFormat format : getAvailableEncodings(type)) + { if (getPriority(format) > 0) - supported.add(format); - return supported; + { + enabled.add(format); + } + } + return enabled; } /** @@ -321,7 +115,7 @@ private Set<MediaFormat> updateSupportedEncodings(MediaType type) * @param clockRate clock rate * @param pref a positive int indicating the preference for that encoding. */ - private void setEncodingPreference( + protected void setEncodingPreference( String encoding, double clockRate, int pref) { @@ -374,37 +168,29 @@ public void setPriority(MediaFormat encoding, int priority) updateSupportedEncodings(); } - + /** - * Sets the priority of the given encoding in the configuration service. - * + * Sets <tt>priority</tt> as the preference associated with + * <tt>encoding</tt> (with a call to <tt>setPriority(MediaFormat, int)</tt> + * for example), and also, if <tt>updateConfig</tt> is <tt>true</tt>, + * updates configuration. * @param encoding the <tt>MediaFormat</tt> specifying the encoding to set * the priority of * @param priority a positive <tt>int</tt> indicating the priority of * <tt>encoding</tt> to set + * @param updateConfig Whether to update configuration or not. */ - public void setPriorityConfig(MediaFormat encoding, int priority) - { - String encodingEncoding = encoding.getEncoding(); + public abstract void setPriority(MediaFormat encoding, int priority, + boolean updateConfig); - // save the settings - LibJitsi.getConfigurationService().setProperty( - PROP_SDP_PREFERENCE - + "." - + encodingEncoding - + "/" - + encoding.getClockRateString(), - priority); - } /** - * Set the priority for a <tt>MediaFormat</tt>. + * Get the priority for a <tt>MediaFormat</tt>. * @param encoding the <tt>MediaFormat</tt> * @return the priority */ public int getPriority(MediaFormat encoding) { - /* * Directly returning encodingPreference.get(encoding) will throw a * NullPointerException if encodingPreferences does not contain a @@ -416,209 +202,7 @@ public int getPriority(MediaFormat encoding) return (priority == null) ? 0 : priority; } - /** - * Register in JMF the custom codecs we provide - */ - private void registerCustomCodecs() - { - // Register the custom codec which haven't already been registered. - @SuppressWarnings("unchecked") - Collection<String> registeredPlugins - = new HashSet<String>( - PlugInManager.getPlugInList( - null, - null, - PlugInManager.CODEC)); - boolean commit = false; - - // Remove JavaRGBToYUV. - PlugInManager.removePlugIn( - "com.sun.media.codec.video.colorspace.JavaRGBToYUV", - PlugInManager.CODEC); - PlugInManager.removePlugIn( - "com.sun.media.codec.video.colorspace.JavaRGBConverter", - PlugInManager.CODEC); - PlugInManager.removePlugIn( - "com.sun.media.codec.video.colorspace.RGBScaler", - PlugInManager.CODEC); - - // Remove JMF's H263 codec. - PlugInManager.removePlugIn( - "com.sun.media.codec.video.vh263.NativeDecoder", - PlugInManager.CODEC); - PlugInManager.removePlugIn( - "com.ibm.media.codec.video.h263.NativeEncoder", - PlugInManager.CODEC); - - // Remove JMF's GSM codec. As working only on some OS. - String gsmCodecPackage = "com.ibm.media.codec.audio.gsm."; - String[] gsmCodecClasses - = new String[] - { - "JavaDecoder", - "JavaDecoder_ms", - "JavaEncoder", - "JavaEncoder_ms", - "NativeDecoder", - "NativeDecoder_ms", - "NativeEncoder", - "NativeEncoder_ms", - "Packetizer" - }; - for(String gsmCodecClass : gsmCodecClasses) - { - PlugInManager.removePlugIn( - gsmCodecPackage + gsmCodecClass, - PlugInManager.CODEC); - } - - /* - * Remove FMJ's JavaSoundCodec because it seems to slow down the - * building of the filter graph and we do not currently seem to need it. - */ - PlugInManager.removePlugIn( - "net.sf.fmj.media.codec.JavaSoundCodec", - PlugInManager.CODEC); - - for (String className : CUSTOM_CODECS) - { - - /* - * A codec with a className of null is configured at compile time to - * not be registered. - */ - if (className == null) - continue; - - if (registeredPlugins.contains(className)) - { - if (logger.isDebugEnabled()) - logger.debug( - "Codec " + className + " is already registered"); - } - else - { - commit = true; - - boolean registered; - Throwable exception = null; - - try - { - Codec codec = (Codec) - Class.forName(className).newInstance(); - - registered = - PlugInManager.addPlugIn( - className, - codec.getSupportedInputFormats(), - codec.getSupportedOutputFormats(null), - PlugInManager.CODEC); - } - catch (Throwable ex) - { - registered = false; - exception = ex; - } - if (registered) - { - if (logger.isDebugEnabled()) - logger.debug( - "Codec " - + className - + " is successfully registered"); - } - else - { - if (logger.isDebugEnabled()) - logger.debug( - "Codec " - + className - + " is NOT succsefully registered", exception); - } - } - } - - /* - * If Jitsi provides a codec which is also provided by FMJ and/or JMF, - * use Jitsi's version. - */ - @SuppressWarnings("unchecked") - Vector<String> codecs - = PlugInManager.getPlugInList(null, null, PlugInManager.CODEC); - - if (codecs != null) - { - boolean setPlugInList = false; - - for (int i = CUSTOM_CODECS.length - 1; i >= 0; i--) - { - String className = CUSTOM_CODECS[i]; - - if (className != null) - { - int classNameIndex = codecs.indexOf(className); - - if (classNameIndex != -1) - { - codecs.remove(classNameIndex); - codecs.add(0, className); - setPlugInList = true; - } - } - } - - if (setPlugInList) - PlugInManager.setPlugInList(codecs, PlugInManager.CODEC); - } - - if (commit && !MediaServiceImpl.isJmfRegistryDisableLoad()) - { - try - { - PlugInManager.commit(); - } - catch (IOException ex) - { - logger.error("Cannot commit to PlugInManager", ex); - } - } - } - - /** - * Register in JMF the custom packages we provide - */ - private void registerCustomPackages() - { - @SuppressWarnings("unchecked") - Vector<String> packages = PackageManager.getProtocolPrefixList(); - boolean loggerIsDebugEnabled = logger.isDebugEnabled(); - - for (String customPackage : CUSTOM_PACKAGES) - { - /* - * Linear search in a loop but it doesn't have to scale since the - * list is always short. - */ - if (!packages.contains(customPackage)) - { - packages.add(customPackage); - if (loggerIsDebugEnabled) - if (logger.isDebugEnabled()) - logger.debug("Adding package : " + customPackage); - } - } - - PackageManager.setProtocolPrefixList(packages); - PackageManager.commitProtocolPrefixList(); - if (loggerIsDebugEnabled) - { - if (logger.isDebugEnabled()) - logger.debug( - "Registering new protocol prefix list: " + packages); - } - } - + /** * Get the available encodings for a specific <tt>MediaType</tt>. * @@ -660,6 +244,7 @@ public MediaFormat[] getSupportedEncodings(MediaType type) default: return MediaUtils.EMPTY_MEDIA_FORMATS; } + return supportedEncodings.toArray( new MediaFormat[supportedEncodings.size()]); @@ -746,8 +331,142 @@ private int compareEncodingPreferences(MediaFormat enc1, MediaFormat enc2) * @return the key in {@link #encodingPreferences} which is associated with * the priority of the specified <tt>encoding</tt> */ - private String getEncodingPreferenceKey(MediaFormat encoding) + protected String getEncodingPreferenceKey(MediaFormat encoding) { return encoding.getEncoding() + "/" + encoding.getClockRateString(); } + + /** + * Loads configuration. + */ + public abstract void loadConfig(); + + /** + * Parses the properties under <tt>prefix</tt> and loads them. + * + * @param prefix The prefix to search the configuration under + */ + public void loadFormatPreferencesFromConfig(String prefix) + { + ConfigurationService cfg = LibJitsi.getConfigurationService(); + + Map<String, String> properties = new HashMap<String, String>(); + + if (cfg != null) + { + for (String pName : cfg.getPropertyNamesByPrefix(prefix, false)) + { + properties.put(pName, + cfg.getString(pName)); + } + loadProperties(properties); + } + } + + + /** + * Parses a <tt>Map<String, String></tt> and updates the format preferences + * according to it. + * The map is expected to have entries in the form of + * (formatString, preference). + * @param properties The <tt>Map</tt> to parse. + */ + public void loadProperties(Map<String, String> properties) + { + + for (String pName + : properties.keySet()) + { + String prefStr = properties.get(pName); + String fmtName; + if(pName.contains(".")) + { + fmtName = pName.substring(pName.lastIndexOf('.') + 1); + } + else + { + fmtName = pName; + } + + // legacy + if (fmtName.contains("sdp")) + { + fmtName = fmtName.replaceAll("sdp", ""); + /* + * If the current version of the property name is also + * associated with a value, ignore the value for the legacy + * one. + */ + if (properties.containsKey(pName.replaceAll("sdp", ""))) + continue; + } + + int preference = -1; + String encoding; + double clockRate; + + try + { + preference = Integer.parseInt(prefStr); + + int encodingClockRateSeparator = fmtName.lastIndexOf('/'); + + if (encodingClockRateSeparator > -1) + { + encoding + = fmtName.substring(0, encodingClockRateSeparator); + clockRate + = Double.parseDouble( + fmtName.substring( + encodingClockRateSeparator + 1)); + } + else + { + encoding = fmtName; + clockRate = MediaFormatFactory.CLOCK_RATE_NOT_SPECIFIED; + } + } + catch (NumberFormatException nfe) + { + logger.warn( + "Failed to parse format (" + + fmtName + + ") or preference (" + + prefStr + + ").", + nfe); + continue; + } + setEncodingPreference(encoding, clockRate, preference); + } + + + // now update the arrays so that they are returned by order of + // preference. + updateSupportedEncodings(); + } + + + /** + * Returns a <tt>Map<String, String></tt> that holds the properties + * corresponding to the current format preferences + * @return A <tt>Map<String, String></tt> that holds the properties + * corresponding to the current format preferences. + */ + public Map<String, String> getEncodingProperties() + { + Map<String, String> encodingProperties = new HashMap<String, String>(); + for(MediaFormat mf : getAvailableEncodings(MediaType.AUDIO)) + { + encodingProperties.put(getEncodingPreferenceKey(mf), + "" + getPriority(mf)); + } + for(MediaFormat mf : getAvailableEncodings(MediaType.VIDEO)) + { + encodingProperties.put(getEncodingPreferenceKey(mf), + "" + getPriority(mf)); + } + return encodingProperties; + } + } diff --git a/src/org/jitsi/service/neomedia/device/MediaDevice.java b/src/org/jitsi/service/neomedia/device/MediaDevice.java index 3d732319..d3efe4e1 100644 --- a/src/org/jitsi/service/neomedia/device/MediaDevice.java +++ b/src/org/jitsi/service/neomedia/device/MediaDevice.java @@ -9,6 +9,7 @@ import java.util.*; import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.format.*; /** @@ -80,4 +81,24 @@ public interface MediaDevice public List<MediaFormat> getSupportedFormats( QualityPreset localPreset, QualityPreset remotePreset); + + /** + * Returns a list of <tt>MediaFormat</tt> instances representing the media + * formats supported by this <tt>MediaDevice</tt> and enabled in + * <tt>encodingConfiguration</tt>. + * + * @param localPreset the preset used to set the send format parameters, + * used for video and settings. + * @param remotePreset the preset used to set the receive format parameters, + * used for video and settings. + * @param encodingConfiguration the <tt>EncodingConfiguration<tt> instance + * to use. + * + * @return the list of <tt>MediaFormat</tt>s supported by this device. + */ + public List<MediaFormat> getSupportedFormats( + QualityPreset localPreset, + QualityPreset remotePreset, + EncodingConfiguration encodingConfiguration); + } -- GitLab