Skip to content
Snippets Groups Projects
MediaServiceImpl.java 55.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * 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;
    
    import java.awt.*;
    import java.awt.event.*;
    import java.beans.*;
    
    import java.util.*;
    import java.util.List;
    
    import javax.media.*;
    import javax.media.control.*;
    import javax.media.format.*;
    import javax.media.protocol.*;
    import javax.swing.*;
    
    
    import net.java.sip.communicator.impl.neomedia.codec.*;
    
    
    import org.jitsi.impl.neomedia.codec.*;
    import org.jitsi.impl.neomedia.codec.video.*;
    import org.jitsi.impl.neomedia.device.*;
    import org.jitsi.impl.neomedia.format.*;
    import org.jitsi.impl.neomedia.transform.sdes.*;
    import org.jitsi.service.configuration.*;
    
    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.*;
    import org.jitsi.util.event.*;
    import org.jitsi.util.swing.*;
    import org.json.*;
    
    
    /**
     * Implements <tt>MediaService</tt> for JMF.
     *
     * @author Lyubomir Marinov
     * @author Dmitri Melnikov
     */
    public class MediaServiceImpl
        extends PropertyChangeNotifier
        implements MediaService
    {
        /**
         * The <tt>Logger</tt> used by the <tt>MediaServiceImpl</tt> class and its
         * instances for logging output.
         */
        private static final Logger logger
            = Logger.getLogger(MediaServiceImpl.class);
    
        /**
         * The name of the <tt>boolean</tt> <tt>ConfigurationService</tt> property
         * which indicates whether the detection of audio <tt>CaptureDevice</tt>s is
         * to be disabled. The default value is <tt>false</tt> i.e. the audio
         * <tt>CaptureDevice</tt>s are detected.
         */
        public static final String DISABLE_AUDIO_SUPPORT_PNAME
    
            = "net.java.sip.communicator.service.media.DISABLE_AUDIO_SUPPORT";
    
    
        /**
         * The name of the <tt>boolean</tt> <tt>ConfigurationService</tt> property
         * which indicates whether the detection of video <tt>CaptureDevice</tt>s is
         * to be disabled. The default value is <tt>false</tt> i.e. the video
         * <tt>CaptureDevice</tt>s are detected.
         */
        public static final String DISABLE_VIDEO_SUPPORT_PNAME
    
            = "net.java.sip.communicator.service.media.DISABLE_VIDEO_SUPPORT";
    
    
        /**
         * The prefix of the property names the values of which specify the dynamic
         * payload type preferences.
         */
        private static final String DYNAMIC_PAYLOAD_TYPE_PREFERENCES_PNAME_PREFIX
    
            = "net.java.sip.communicator.impl.neomedia.dynamicPayloadTypePreferences";
    
    
        /**
         * The value of the <tt>devices</tt> property of <tt>MediaServiceImpl</tt>
         * when no <tt>MediaDevice</tt>s are available. Explicitly defined in order
         * to reduce unnecessary allocations.
         */
        private static final List<MediaDevice> EMPTY_DEVICES
            = Collections.emptyList();
    
    
        /**
         * The name of the <tt>System</tt> boolean property which specifies whether
         * the loading of the JMF/FMJ <tt>Registry</tt> is to be disabled. 
         */
        private static final String JMF_REGISTRY_DISABLE_LOAD
            = "net.sf.fmj.utility.JmfRegistry.disableLoad";
    
        /**
         * The indicator which determines whether the loading of the JMF/FMJ
         * <tt>Registry</tt> is disabled.
         */
        private static boolean jmfRegistryDisableLoad;
    
        /**
         * The indicator which determined whether
         * {@link #postInitializeOnce(MediaServiceImpl)} has been executed in order
         * to perform one-time initialization after initializing the first instance
         * of <tt>MediaServiceImpl</tt>.
         */
        private static boolean postInitializeOnce;
    
    
        /**
         * The prefix that is used to store configuration for encodings preference.
         */
        private static final String ENCODING_CONFIG_PROP_PREFIX
            = "net.java.sip.communicator.impl.neomedia.codec.EncodingConfiguration";
    
    
        /**
         * The <tt>CaptureDevice</tt> user choices such as the default audio and
         * video capture devices.
         */
        private final DeviceConfiguration deviceConfiguration
            = new DeviceConfiguration();
    
        /**
         * The <tt>PropertyChangeListener</tt> which listens to
         * {@link #deviceConfiguration}.
         */
        private final PropertyChangeListener
            deviceConfigurationPropertyChangeListener
                = new PropertyChangeListener()
                        {
                            public void propertyChange(PropertyChangeEvent event)
                            {
                                deviceConfigurationPropertyChange(event);
                            }
                        };
    
        /**
         * The list of audio <tt>MediaDevice</tt>s reported by this instance when
         * its {@link MediaService#getDevices(MediaType, MediaUseCase)} method is
         * called with an argument {@link MediaType#AUDIO}.
         */
        private final List<MediaDeviceImpl> audioDevices
            = new ArrayList<MediaDeviceImpl>();
    
        /**
    
         * The {@link EncodingConfiguration} instance that holds the current (global)
         * list of formats and their preference.
    
        private final EncodingConfiguration currentEncodingConfiguration;
    
    
        /**
         * The <tt>MediaFormatFactory</tt> through which <tt>MediaFormat</tt>
         * instances may be created for the purposes of working with the
         * <tt>MediaStream</tt>s created by this <tt>MediaService</tt>.
         */
        private MediaFormatFactory formatFactory;
    
        /**
         * The one and only <tt>MediaDevice</tt> instance with
         * <tt>MediaDirection</tt> not allowing sending and <tt>MediaType</tt> equal
         * to <tt>AUDIO</tt>.
         */
        private MediaDevice nonSendAudioDevice;
    
        /**
         * The one and only <tt>MediaDevice</tt> instance with
         * <tt>MediaDirection</tt> not allowing sending and <tt>MediaType</tt> equal
         * to <tt>VIDEO</tt>.
         */
        private MediaDevice nonSendVideoDevice;
    
        /**
         * The list of video <tt>MediaDevice</tt>s reported by this instance when
         * its {@link MediaService#getDevices(MediaType, MediaUseCase)} method is
         * called with an argument {@link MediaType#VIDEO}.
         */
        private final List<MediaDeviceImpl> videoDevices
            = new ArrayList<MediaDeviceImpl>();
    
        /**
         * A {@link Map} that binds indicates whatever preferences this
         * media service implementation may have for the RTP payload type numbers
         * that get dynamically assigned to {@link MediaFormat}s with no static
         * payload type. The method is useful for formats such as "telephone-event"
         * for example that is statically assigned the 101 payload type by some
         * legacy systems. Signalling protocol implementations such as SIP and XMPP
         * should make sure that, whenever this is possible, they assign to formats
         * the dynamic payload type returned in this {@link Map}.
         */
        private static Map<MediaFormat, Byte> dynamicPayloadTypePreferences;
    
        /**
         * The volume control of the media service playback.
         */
        private static VolumeControl outputVolumeControl;
    
        /**
         * The volume control of the media service capture.
         */
        private static VolumeControl inputVolumeControl;
    
        /**
         * Listeners interested in Recorder events without the need to
         * have access to their instances.
         */
        private final List<Recorder.Listener> recorderListeners =
                new ArrayList<Recorder.Listener>();
    
    
        /**
         * Initializes a new <tt>MediaServiceImpl</tt> instance.
         */
        public MediaServiceImpl()
        {
    
            /*
             * XXX The deviceConfiguration is initialized and referenced by this
             * instance so adding deviceConfigurationPropertyChangeListener does not
             * need a matching removal.
             */
            deviceConfiguration.addPropertyChangeListener(
                    deviceConfigurationPropertyChangeListener);
    
    
            currentEncodingConfiguration
                 = new EncodingConfigurationConfigImpl(ENCODING_CONFIG_PROP_PREFIX);
    
    
            /*
             * Perform one-time initialization after initializing the first instance
             * of MediaServiceImpl.
             */
            synchronized (MediaServiceImpl.class)
            {
                if (!postInitializeOnce)
                {
                    postInitializeOnce = true;
                    postInitializeOnce(this);
                }
            }
    
        }
    
        /**
         * Create a <tt>MediaStream</tt> which will use a specific
         * <tt>MediaDevice</tt> for capture and playback of media. The new instance
         * will not have a <tt>StreamConnector</tt> at the time of its construction
         * and a <tt>StreamConnector</tt> will be specified later on in order to
         * enable the new instance to send and receive media.
         *
         * @param device the <tt>MediaDevice</tt> to be used by the new instance for
         * capture and playback of media
         * @return a newly-created <tt>MediaStream</tt> which will use the specified
         * <tt>device</tt> for capture and playback of media
         * @see MediaService#createMediaStream(MediaDevice)
         */
        public MediaStream createMediaStream(MediaDevice device)
        {
            return createMediaStream(null, device);
        }
    
    
        /**
         * {@inheritDoc}
         *
         * Implements {@link MediaService#createMediaStream(MediaType)}. Initializes
         * a new <tt>AudioMediaStreamImpl</tt> or <tt>VideoMediaStreamImpl</tt> in
         * accord with <tt>mediaType</tt>
         */
        public MediaStream createMediaStream(MediaType mediaType)
        {
            return createMediaStream(mediaType, null, null, null);
        }
    
    
        /**
         * Creates a new <tt>MediaStream</tt> instance which will use the specified
         * <tt>MediaDevice</tt> for both capture and playback of media exchanged
         * via the specified <tt>StreamConnector</tt>.
         *
         * @param connector the <tt>StreamConnector</tt> that the new
         * <tt>MediaStream</tt> instance is to use for sending and receiving media
         * @param device the <tt>MediaDevice</tt> that the new <tt>MediaStream</tt>
         * instance is to use for both capture and playback of media exchanged via
         * the specified <tt>connector</tt>
         * @return a new <tt>MediaStream</tt> instance
         * @see MediaService#createMediaStream(StreamConnector, MediaDevice)
         */
        public MediaStream createMediaStream(
                StreamConnector connector,
                MediaDevice device)
        {
            return createMediaStream(connector, device, null);
        }
    
        /**
         * Creates a new <tt>MediaStream</tt> instance which will use the specified
         * <tt>MediaDevice</tt> for both capture and playback of media exchanged
         * via the specified <tt>StreamConnector</tt>.
         *
         * @param connector the <tt>StreamConnector</tt> that the new
         * <tt>MediaStream</tt> instance is to use for sending and receiving media
         * @param device the <tt>MediaDevice</tt> that the new <tt>MediaStream</tt>
         * instance is to use for both capture and playback of media exchanged via
         * the specified <tt>connector</tt>
         * @param srtpControl a control which is already created, used to control
    
         *
         * @return a new <tt>MediaStream</tt> instance
         * @see MediaService#createMediaStream(StreamConnector, MediaDevice)
         */
        public MediaStream createMediaStream(
                StreamConnector connector,
                MediaDevice device,
                SrtpControl srtpControl)
        {
    
            return createMediaStream(null, connector, device, srtpControl);
        }
    
        /**
         * Initializes a new <tt>MediaStream</tt> instance. The method is the actual
         * implementation to which the public <tt>createMediaStream</tt> methods of
         * <tt>MediaServiceImpl</tt> delegate.
         *
         * @param mediaType the <tt>MediaType</tt> of the new <tt>MediaStream</tt>
         * instance to be initialized. If <tt>null</tt>, <tt>device</tt> must be
         * non-<tt>null</tt> and its {@link MediaDevice#getMediaType()} will be used
         * to determine the <tt>MediaType</tt> of the new instance. If
         * non-<tt>null</tt>, <tt>device</tt> may be <tt>null</tt>. If
         * non-<tt>null</tt> and <tt>device</tt> is non-<tt>null</tt>, the
         * <tt>MediaType</tt> of <tt>device</tt> must be (equal to)
         * <tt>mediaType</tt>.
         * @param connector the <tt>StreamConnector</tt> to be used by the new
         * instance if non-<tt>null</tt>
         * @param device the <tt>MediaDevice</tt> to be used by the instance if
         * non-<tt>null</tt>
         * @param srtpControl the <tt>SrtpControl</tt> to be used by the new
         * instance if non-<tt>null</tt>
         * @return a new <tt>MediaStream</tt> instance
         */
        private MediaStream createMediaStream(
                MediaType mediaType,
                StreamConnector connector,
                MediaDevice device,
                SrtpControl srtpControl)
        {
            // Make sure that mediaType and device are in accord.
            if (mediaType == null)
            {
                if (device == null)
                    throw new NullPointerException("device");
                else
                    mediaType = device.getMediaType();
            }
            else if ((device != null) && !mediaType.equals(device.getMediaType()))
                throw new IllegalArgumentException("device");
    
            switch (mediaType)
    
            {
            case AUDIO:
                return new AudioMediaStreamImpl(connector, device, srtpControl);
            case VIDEO:
                return new VideoMediaStreamImpl(connector, device, srtpControl);
            default:
                return null;
            }
        }
    
        /**
         * Creates a new <tt>MediaDevice</tt> which uses a specific
         * <tt>MediaDevice</tt> to capture and play back media and performs mixing
         * of the captured media and the media played back by any other users of the
         * returned <tt>MediaDevice</tt>. For the <tt>AUDIO</tt> <tt>MediaType</tt>,
         * the returned device is commonly referred to as an audio mixer. The
         * <tt>MediaType</tt> of the returned <tt>MediaDevice</tt> is the same as
         * the <tt>MediaType</tt> of the specified <tt>device</tt>.
         *
         * @param device the <tt>MediaDevice</tt> which is to be used by the
         * returned <tt>MediaDevice</tt> to actually capture and play back media
         * @return a new <tt>MediaDevice</tt> instance which uses <tt>device</tt> to
         * capture and play back media and performs mixing of the captured media and
         * the media played back by any other users of the returned
         * <tt>MediaDevice</tt> instance
         * @see MediaService#createMixer(MediaDevice)
         */
        public MediaDevice createMixer(MediaDevice device)
        {
            switch (device.getMediaType())
            {
            case AUDIO:
                return new AudioMixerMediaDevice((AudioMediaDeviceImpl) device);
            case VIDEO:
                return new VideoTranslatorMediaDevice((MediaDeviceImpl) device);
            default:
                /*
                 * TODO If we do not support mixing, should we return null or rather
                 * a MediaDevice with INACTIVE MediaDirection?
                 */
                return null;
            }
        }
    
        /**
         * Gets the default <tt>MediaDevice</tt> for the specified
         * <tt>MediaType</tt>.
         *
         * @param mediaType a <tt>MediaType</tt> value indicating the type of media
         * to be handled by the <tt>MediaDevice</tt> to be obtained
         * @param useCase the <tt>MediaUseCase</tt> to obtain the
         * <tt>MediaDevice</tt> list for
         * @return the default <tt>MediaDevice</tt> for the specified
         * <tt>mediaType</tt> if such a <tt>MediaDevice</tt> exists; otherwise,
         * <tt>null</tt>
         * @see MediaService#getDefaultDevice(MediaType, MediaUseCase)
         */
        public MediaDevice getDefaultDevice(
                MediaType mediaType,
                MediaUseCase useCase)
        {
            CaptureDeviceInfo captureDeviceInfo;
    
            switch (mediaType)
            {
            case AUDIO:
                captureDeviceInfo
                    = getDeviceConfiguration().getAudioCaptureDevice();
                break;
            case VIDEO:
                captureDeviceInfo
                    = getDeviceConfiguration().getVideoCaptureDevice(useCase);
                break;
            default:
                captureDeviceInfo = null;
                break;
            }
    
            MediaDevice defaultDevice = null;
    
            if (captureDeviceInfo != null)
            {
                for (MediaDevice device : getDevices(mediaType, useCase))
                {
                    if ((device instanceof MediaDeviceImpl)
                            && captureDeviceInfo.equals(
                                    ((MediaDeviceImpl) device)
                                        .getCaptureDeviceInfo()))
                    {
                        defaultDevice = device;
                        break;
                    }
                }
            }
            if (defaultDevice == null)
            {
                switch (mediaType)
                {
                case AUDIO:
                    defaultDevice = getNonSendAudioDevice();
                    break;
                case VIDEO:
                    defaultDevice = getNonSendVideoDevice();
                    break;
                default:
                    /*
                     * There is no MediaDevice with direction which does not allow
                     * sending and mediaType other than AUDIO and VIDEO.
                     */
                    break;
                }
            }
    
            return defaultDevice;
        }
    
        /**
         * Gets the <tt>CaptureDevice</tt> user choices such as the default audio
         * and video capture devices.
         *
         * @return the <tt>CaptureDevice</tt> user choices such as the default audio
         * and video capture devices.
         */
        public DeviceConfiguration getDeviceConfiguration()
        {
            return deviceConfiguration;
        }
    
        /**
         * Gets a list of the <tt>MediaDevice</tt>s known to this
         * <tt>MediaService</tt> and handling the specified <tt>MediaType</tt>.
         *
         * @param mediaType the <tt>MediaType</tt> to obtain the
         * <tt>MediaDevice</tt> list for
         * @param useCase the <tt>MediaUseCase</tt> to obtain the
         * <tt>MediaDevice</tt> list for
         * @return a new <tt>List</tt> of <tt>MediaDevice</tt>s known to this
         * <tt>MediaService</tt> and handling the specified <tt>MediaType</tt>. The
         * returned <tt>List</tt> is a copy of the internal storage and,
         * consequently, modifications to it do not affect this instance. Despite
         * the fact that a new <tt>List</tt> instance is returned by each call to
         * this method, the <tt>MediaDevice</tt> instances are the same if they are
         * still known to this <tt>MediaService</tt> to be available.
         * @see MediaService#getDevices(MediaType, MediaUseCase)
         */
        public List<MediaDevice> getDevices(
                MediaType mediaType,
                MediaUseCase useCase)
        {
            List<CaptureDeviceInfo> cdis;
            List<MediaDeviceImpl> privateDevices;
    
            if (MediaType.VIDEO.equals(mediaType))
            {
                /*
                 * In case a video capture device has been added to or removed from
                 * system (i.e. webcam, monitor, etc.), rescan the video capture
                 * devices.
                 */
                DeviceSystem.initializeDeviceSystems(MediaType.VIDEO);
            }
    
            switch (mediaType)
            {
            case AUDIO:
                cdis = getDeviceConfiguration().getAvailableAudioCaptureDevices();
                privateDevices = audioDevices;
                break;
            case VIDEO:
                cdis
                    = getDeviceConfiguration().getAvailableVideoCaptureDevices(
                            useCase);
                privateDevices = videoDevices;
                break;
            default:
                /*
                 * MediaService does not understand MediaTypes other than AUDIO and
                 * VIDEO.
                 */
                return EMPTY_DEVICES;
            }
    
            List<MediaDevice> publicDevices;
    
            synchronized (privateDevices)
            {
                if ((cdis == null)
                        || (cdis.size() <= 0))
                    privateDevices.clear();
                else
                {
                    Iterator<MediaDeviceImpl> deviceIter
                        = privateDevices.iterator();
    
                    while (deviceIter.hasNext())
                    {
                        Iterator<CaptureDeviceInfo> cdiIter = cdis.iterator();
                        CaptureDeviceInfo captureDeviceInfo
                            = deviceIter.next().getCaptureDeviceInfo();
                        boolean deviceIsFound = false;
    
                        while (cdiIter.hasNext())
                        {
                            if (captureDeviceInfo.equals(cdiIter.next()))
                            {
                                deviceIsFound = true;
                                cdiIter.remove();
                                break;
                            }
                        }
                        if (!deviceIsFound)
                            deviceIter.remove();
                    }
    
                    for (CaptureDeviceInfo cdi : cdis)
                    {
                        if (cdi == null)
                            continue;
    
                        MediaDeviceImpl device;
    
                        switch (mediaType)
                        {
                        case AUDIO:
                            device = new AudioMediaDeviceImpl(cdi);
                            break;
                        case VIDEO:
                            device
                                = new MediaDeviceImpl(cdi, mediaType);
                            break;
                        default:
                            device = null;
                            break;
                        }
                        if (device != null)
                            privateDevices.add(device);
                    }
                }
    
                publicDevices = new ArrayList<MediaDevice>(privateDevices);
            }
    
            /*
             * If there are no MediaDevice instances of the specified mediaType,
             * make sure that there is at least one MediaDevice which does not allow
             * sending.
             */
            if (publicDevices.isEmpty())
            {
                MediaDevice nonSendDevice;
    
                switch (mediaType)
                {
                case AUDIO:
                    nonSendDevice = getNonSendAudioDevice();
                    break;
                case VIDEO:
                    nonSendDevice = getNonSendVideoDevice();
                    break;
                default:
                    /*
                     * There is no MediaDevice with direction not allowing sending
                     * and mediaType other than AUDIO and VIDEO.
                     */
                    nonSendDevice = null;
                    break;
                }
                if (nonSendDevice != null)
                    publicDevices.add(nonSendDevice);
            }
    
            return publicDevices;
        }
    
        /**
    
         * Returns the current encoding configuration -- the instance that contains
         * the global settings. Note that any changes made to this instance will
         * have immediate effect on the configuration.
    
         * @return the current encoding configuration -- the instance that contains
         * the global settings.
    
        public EncodingConfiguration getCurrentEncodingConfiguration()
    
        }
    
        /**
         * Gets the <tt>MediaFormatFactory</tt> through which <tt>MediaFormat</tt>
         * instances may be created for the purposes of working with the
         * <tt>MediaStream</tt>s created by this <tt>MediaService</tt>.
         *
         * @return the <tt>MediaFormatFactory</tt> through which
         * <tt>MediaFormat</tt> instances may be created for the purposes of working
         * with the <tt>MediaStream</tt>s created by this <tt>MediaService</tt>
         * @see MediaService#getFormatFactory()
         */
        public MediaFormatFactory getFormatFactory()
        {
            if (formatFactory == null)
                formatFactory = new MediaFormatFactoryImpl();
            return formatFactory;
        }
    
        /**
         * Gets the one and only <tt>MediaDevice</tt> instance with
         * <tt>MediaDirection</tt> not allowing sending and <tt>MediaType</tt> equal
         * to <tt>AUDIO</tt>.
         *
         * @return the one and only <tt>MediaDevice</tt> instance with
         * <tt>MediaDirection</tt> not allowing sending and <tt>MediaType</tt> equal
         * to <tt>AUDIO</tt>
         */
        private MediaDevice getNonSendAudioDevice()
        {
            if (nonSendAudioDevice == null)
                nonSendAudioDevice = new AudioMediaDeviceImpl();
            return nonSendAudioDevice;
        }
    
        /**
         * Gets the one and only <tt>MediaDevice</tt> instance with
         * <tt>MediaDirection</tt> not allowing sending and <tt>MediaType</tt> equal
         * to <tt>VIDEO</tt>.
         *
         * @return the one and only <tt>MediaDevice</tt> instance with
         * <tt>MediaDirection</tt> not allowing sending and <tt>MediaType</tt> equal
         * to <tt>VIDEO</tt>
         */
        private MediaDevice getNonSendVideoDevice()
        {
            if (nonSendVideoDevice == null)
                nonSendVideoDevice = new MediaDeviceImpl(MediaType.VIDEO);
            return nonSendVideoDevice;
        }
    
        /**
         * Initializes a new <tt>ZrtpControl</tt> instance which is to control all
         * ZRTP options.
         *
         * @return a new <tt>ZrtpControl</tt> instance which is to control all ZRTP
         * options
         */
        public ZrtpControl createZrtpControl()
        {
            return new ZrtpControlImpl();
        }
    
        /**
         * Initializes a new <tt>SDesControl</tt> instance which is to control all
         * SDes options.
         *
         * @return a new <tt>SDesControl</tt> instance which is to control all SDes
         * options
         */
        public SDesControl createSDesControl()
        {
            return new SDesControlImpl();
        }
    
        /**
         * Gets the <tt>VolumeControl</tt> which controls the volume level of audio
         * output/playback.
         *
         * @return the <tt>VolumeControl</tt> which controls the volume level of
         * audio output/playback
         * @see MediaService#getOutputVolumeControl()
         */
        public VolumeControl getOutputVolumeControl()
        {
            if (outputVolumeControl == null)
            {
                outputVolumeControl
                    = new AbstractVolumeControl(
                            VolumeControl.PLAYBACK_VOLUME_LEVEL_PROPERTY_NAME);
            }
            return outputVolumeControl;
        }
    
        /**
         * Gets the <tt>VolumeControl</tt> which controls the volume level of audio
         * input/capture.
         *
         * @return the <tt>VolumeControl</tt> which controls the volume level of
         * audio input/capture
         * @see MediaService#getInputVolumeControl()
         */
        public VolumeControl getInputVolumeControl()
        {
            if (inputVolumeControl == null)
            {
                inputVolumeControl
                    = new AbstractVolumeControl(
                            VolumeControl.CAPTURE_VOLUME_LEVEL_PROPERTY_NAME);
            }
            return inputVolumeControl;
        }
    
        /**
         * Get available screens.
         *
         * @return screens
         */
        public List<ScreenDevice> getAvailableScreenDevices()
        {
    
            ScreenDevice screens[] = ScreenDeviceImpl.getAvailableScreenDevices();
    
            List<ScreenDevice> screenList;
    
            if ((screens != null) && (screens.length != 0))
                screenList = new ArrayList<ScreenDevice>(Arrays.asList(screens));
            else
                screenList = Collections.emptyList();
            return screenList;
        }
    
        /**
         * Get default screen device.
         *
         * @return default screen device
         */
        public ScreenDevice getDefaultScreenDevice()
        {
            List<ScreenDevice> screens = getAvailableScreenDevices();
            int width = 0;
            int height = 0;
            ScreenDevice best = null;
    
            for (ScreenDevice screen : screens)
            {
                java.awt.Dimension res = screen.getSize();
    
                if ((res != null) && ((width < res.width) || (height < res.height)))
                {
                    width = res.width;
                    height = res.height;
                    best = screen;
                }
            }
            return best;
        }
    
        /**
         * Creates a new <tt>Recorder</tt> instance that can be used to record a
         * call which captures and plays back media using a specific
         * <tt>MediaDevice</tt>.
         *
         * @param device the <tt>MediaDevice</tt> which is used for media capture
         * and playback by the call to be recorded
         * @return a new <tt>Recorder</tt> instance that can be used to record a
         * call which captures and plays back media using the specified
         * <tt>MediaDevice</tt>
         * @see MediaService#createRecorder(MediaDevice)
         */
        public Recorder createRecorder(MediaDevice device)
        {
            if (device instanceof AudioMixerMediaDevice)
                return new RecorderImpl((AudioMixerMediaDevice) device);
            else
                return null;
        }
    
        /**
         * Returns a {@link Map} that binds indicates whatever preferences this
         * media service implementation may have for the RTP payload type numbers
         * that get dynamically assigned to {@link MediaFormat}s with no static
         * payload type. The method is useful for formats such as "telephone-event"
         * for example that is statically assigned the 101 payload type by some
         * legacy systems. Signaling protocol implementations such as SIP and XMPP
         * should make sure that, whenever this is possible, they assign to formats
         * the dynamic payload type returned in this {@link Map}.
         *
         * @return a {@link Map} binding some formats to a preferred dynamic RTP
         * payload type number.
         */
        public Map<MediaFormat, Byte> getDynamicPayloadTypePreferences()
        {
            if(dynamicPayloadTypePreferences == null)
            {
                dynamicPayloadTypePreferences = new HashMap<MediaFormat, Byte>();
    
                /*
                 * Set the dynamicPayloadTypePreferences to their default values. If
                 * the user chooses to override them through the
                 * ConfigurationService, they will be overwritten later on.
                 */
                MediaFormat telephoneEvent
                    = MediaUtils.getMediaFormat("telephone-event", 8000);
                if (telephoneEvent != null)
                    dynamicPayloadTypePreferences.put(telephoneEvent, (byte) 101);
    
                MediaFormat h264
                    = MediaUtils.getMediaFormat(
                            "H264",
                            VideoMediaFormatImpl.DEFAULT_CLOCK_RATE);
                if (h264 != null)
                    dynamicPayloadTypePreferences.put(h264, (byte) 99);
    
                /*
                 * Try to load dynamicPayloadTypePreferences from the
                 * ConfigurationService.
                 */
    
                ConfigurationService cfg = LibJitsi.getConfigurationService();
    
    
                if (cfg != null)
                {
                    String prefix = DYNAMIC_PAYLOAD_TYPE_PREFERENCES_PNAME_PREFIX;
                    List<String> propertyNames
                        = cfg.getPropertyNamesByPrefix(prefix, true);
    
                    for (String propertyName : propertyNames)
                    {
                        /*
                         * The dynamic payload type is the name of the property name
                         * and the format which prefers it is the property value.
                         */
                        byte dynamicPayloadTypePreference = 0;
                        Throwable exception = null;
    
                        try
                        {
                            dynamicPayloadTypePreference
                                = Byte.parseByte(
                                        propertyName.substring(
                                                prefix.length() + 1));
                        }
                        catch (IndexOutOfBoundsException ioobe)
                        {
                            exception = ioobe;
                        }
                        catch (NumberFormatException nfe)
                        {
                            exception = nfe;
                        }
                        if (exception != null)
                        {
                            logger.warn(
                                    "Ignoring dynamic payload type preference"
                                        + " which could not be parsed: "
                                        + propertyName,
                                    exception);
                            continue;
                        }
    
                        String source = cfg.getString(propertyName);
    
                        if ((source != null) && (source.length() != 0))
                        {
                            try
                            {
                                JSONObject json = new JSONObject(source);
                                String encoding
                                    = json.getString(
                                            MediaFormatImpl.ENCODING_PNAME);
                                int clockRate
                                    = json.getInt(MediaFormatImpl.CLOCK_RATE_PNAME);
                                Map<String, String> fmtps
                                    = new HashMap<String, String>();
    
                                if (json.has(
                                        MediaFormatImpl.FORMAT_PARAMETERS_PNAME))
                                {
                                    JSONObject jsonFmtps
                                        = json.getJSONObject(
                                                MediaFormatImpl
                                                    .FORMAT_PARAMETERS_PNAME);
                                    Iterator<?> jsonFmtpsIter = jsonFmtps.keys();
    
                                    while (jsonFmtpsIter.hasNext())
                                    {
                                        String key
                                            = jsonFmtpsIter.next().toString();
                                        String value = jsonFmtps.getString(key);
    
                                        fmtps.put(key, value);
                                    }
                                }
    
                                MediaFormat mediaFormat
                                    = MediaUtils.getMediaFormat(
                                            encoding, clockRate,
                                            fmtps);
    
                                if (mediaFormat != null)
                                {
                                    dynamicPayloadTypePreferences.put(
                                            mediaFormat,
                                            dynamicPayloadTypePreference);
                                }
                            }
                            catch (JSONException jsone)
                            {
                                logger.warn(
                                        "Ignoring dynamic payload type preference"
                                            + " which could not be parsed: "
                                            + source,
                                        jsone);
                            }
                        }
                    }
                }
            }
            return dynamicPayloadTypePreferences;
        }
    
        /**
         * Creates a preview component for the specified device(video device) used
         * to show video preview from that device.
         *
         * @param device the video device
         * @param preferredWidth the width we prefer for the component
         * @param preferredHeight the height we prefer for the component
         * @return the preview component.
         */
        public Object getVideoPreviewComponent(
                MediaDevice device,
                int preferredWidth, int preferredHeight)
        {
    
            ResourceManagementService resources
                = LibJitsi.getResourceManagementService();
            String noPreviewText
                = (resources == null)
                    ? ""
                    : resources.getI18NString("impl.media.configform.NO_PREVIEW");
            JLabel noPreview = new JLabel(noPreviewText);
    
    
            noPreview.setHorizontalAlignment(SwingConstants.CENTER);
            noPreview.setVerticalAlignment(SwingConstants.CENTER);
    
    
            final JComponent videoContainer = new VideoContainer(noPreview, false);
    
    
            if ((preferredWidth > 0) && (preferredHeight > 0))
                videoContainer.setPreferredSize(
                        new Dimension(preferredWidth, preferredHeight));
    
            try
            {
                CaptureDeviceInfo captureDeviceInfo;
    
                if ((device != null) &&