CaptureDeviceInfo this instance started to create
+ * the preview of.
+ * + * Because the creation of the preview is asynchronous, it's possible to + * request the preview of one and the same device multiple times. Which may + * lead to failures because of, for example, busy devices and/or resources + * (as is the case with LTI-CIVIL and video4linux2). + *
+ */ + private CaptureDeviceInfo videoDeviceInPreview; + + /** + * ThePlayer depicting the preview of the currently selected
+ * CaptureDeviceInfo.
+ */
+ private Player videoPlayerInPreview;
+
+ /**
+ * Creates the panel.
+ */
+ public MediaConfigurationPanel()
+ {
+ super(new GridLayout(0, 1, HGAP, VGAP));
+
+ int[] types
+ = new int[]
+ {
+ DeviceConfigurationComboBoxModel.AUDIO,
+ DeviceConfigurationComboBoxModel.VIDEO
+ };
+
+ for (int type : types)
+ add(createControls(type));
+ }
+
+ private void controllerUpdateForPreview(ControllerEvent event,
+ Container videoContainer)
+ {
+ if (event instanceof RealizeCompleteEvent)
+ {
+ Player player = (Player) event.getSourceController();
+ Component video = player.getVisualComponent();
+
+ showPreview(videoContainer, video, player);
+ }
+ }
+
+ private void createPortAudioControls(Container portAudioPanel)
+ {
+ portAudioPanel.add(new JLabel(getLabelText(
+ DeviceConfigurationComboBoxModel.AUDIO_CAPTURE)));
+ JComboBox captureCombo = new JComboBox();
+ captureCombo.setEditable(false);
+ captureCombo.setModel(
+ new DeviceConfigurationComboBoxModel(
+ mediaService.getDeviceConfiguration(),
+ DeviceConfigurationComboBoxModel.AUDIO_CAPTURE));
+ portAudioPanel.add(captureCombo);
+
+ portAudioPanel.add(new JLabel(getLabelText(
+ DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK)));
+ JComboBox playbackCombo = new JComboBox();
+ playbackCombo.setEditable(false);
+ playbackCombo.setModel(
+ new DeviceConfigurationComboBoxModel(
+ mediaService.getDeviceConfiguration(),
+ DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK));
+ portAudioPanel.add(playbackCombo);
+
+ portAudioPanel.add(new JLabel(getLabelText(
+ DeviceConfigurationComboBoxModel.AUDIO_NOTIFY)));
+ JComboBox notifyCombo = new JComboBox();
+ notifyCombo.setEditable(false);
+ notifyCombo.setModel(
+ new DeviceConfigurationComboBoxModel(
+ mediaService.getDeviceConfiguration(),
+ DeviceConfigurationComboBoxModel.AUDIO_NOTIFY));
+ portAudioPanel.add(notifyCombo);
+ }
+
+ private Component createControls(int type)
+ {
+ final JComboBox comboBox = new JComboBox();
+ comboBox.setEditable(false);
+ comboBox
+ .setModel(
+ new DeviceConfigurationComboBoxModel(
+ mediaService.getDeviceConfiguration(),
+ type));
+
+ /*
+ * We provide additional configuration properties for PortAudio such as
+ * input audio device, output audio device and audio device for playback
+ * of notifications.
+ */
+ final Container portAudioPanel;
+ if (type == DeviceConfigurationComboBoxModel.AUDIO)
+ {
+ portAudioPanel
+ = new TransparentPanel(new GridLayout(3, 2, HGAP, VGAP));
+
+ comboBox.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(e.getStateChange() == ItemEvent.SELECTED)
+ {
+ if(DeviceConfiguration
+ .AUDIO_SYSTEM_PORTAUDIO.equals(e.getItem()))
+ {
+ createPortAudioControls(portAudioPanel);
+ }
+ else
+ {
+ portAudioPanel.removeAll();
+
+ revalidate();
+ repaint();
+ }
+ }
+ }
+ });
+ if (DeviceConfiguration
+ .AUDIO_SYSTEM_PORTAUDIO.equals(comboBox.getSelectedItem()))
+ createPortAudioControls(portAudioPanel);
+ }
+ else
+ portAudioPanel = null;
+
+ JLabel label = new JLabel(getLabelText(type));
+ label.setDisplayedMnemonic(getDisplayedMnemonic(type));
+ label.setLabelFor(comboBox);
+
+ Container firstContainer = new TransparentPanel(new GridBagLayout());
+ GridBagConstraints firstConstraints = new GridBagConstraints();
+ firstConstraints.anchor = GridBagConstraints.NORTHWEST;
+ firstConstraints.gridx = 0;
+ firstConstraints.gridy = 0;
+ firstConstraints.weightx = 0;
+ firstContainer.add(label, firstConstraints);
+ firstConstraints.gridx = 1;
+ firstConstraints.weightx = 1;
+ firstContainer.add(comboBox, firstConstraints);
+
+ if (portAudioPanel != null)
+ {
+ firstConstraints.gridx = 0;
+ firstConstraints.gridy = 1;
+ firstConstraints.weightx = 1;
+ firstConstraints.gridwidth = 2;
+ firstConstraints.insets = new Insets(VGAP, 0, 0, 0);
+ firstContainer.add(portAudioPanel, firstConstraints);
+ }
+
+ Container secondContainer =
+ new TransparentPanel(new GridLayout(1, 0, HGAP, VGAP));
+ secondContainer.add(createPreview(type, comboBox));
+ secondContainer.add(createEncodingControls(type));
+
+ Container container = new TransparentPanel(new GridBagLayout());
+ GridBagConstraints constraints = new GridBagConstraints();
+ constraints.fill = GridBagConstraints.HORIZONTAL;
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+ constraints.weightx = 1;
+ constraints.weighty = 0;
+ container.add(firstContainer, constraints);
+ constraints.fill = GridBagConstraints.BOTH;
+ constraints.gridy = 1;
+ constraints.weighty = 1;
+ container.add(secondContainer, constraints);
+
+ return container;
+ }
+
+ private Component createEncodingControls(int type)
+ {
+ ResourceManagementService resources = NeomediaActivator.getResources();
+ String key;
+
+ final JTable table = new JTable();
+ table.setShowGrid(false);
+ table.setTableHeader(null);
+
+ key = "impl.media.configform.ENCODINGS";
+ JLabel label = new JLabel(resources.getI18NString(key));
+ label.setDisplayedMnemonic(resources.getI18nMnemonic(key));
+ label.setLabelFor(table);
+
+ key = "impl.media.configform.UP";
+ final JButton upButton = new JButton(resources.getI18NString(key));
+ upButton.setMnemonic(resources.getI18nMnemonic(key));
+ upButton.setOpaque(false);
+
+ key = "impl.media.configform.DOWN";
+ final JButton downButton = new JButton(resources.getI18NString(key));
+ downButton.setMnemonic(resources.getI18nMnemonic(key));
+ downButton.setOpaque(false);
+
+ Container buttonBar = new TransparentPanel(new GridLayout(0, 1));
+ buttonBar.add(upButton);
+ buttonBar.add(downButton);
+
+ Container container = new TransparentPanel(new GridBagLayout());
+ GridBagConstraints constraints = new GridBagConstraints();
+ constraints.anchor = GridBagConstraints.NORTHWEST;
+ constraints.fill = GridBagConstraints.HORIZONTAL;
+ constraints.gridwidth = 2;
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+ constraints.weightx = 0;
+ constraints.weighty = 0;
+ container.add(label, constraints);
+ constraints.anchor = GridBagConstraints.CENTER;
+ constraints.fill = GridBagConstraints.BOTH;
+ constraints.gridwidth = 1;
+ constraints.gridx = 0;
+ constraints.gridy = 1;
+ constraints.weightx = 1;
+ constraints.weighty = 1;
+ container.add(new JScrollPane(table), constraints);
+ constraints.anchor = GridBagConstraints.NORTHEAST;
+ constraints.fill = GridBagConstraints.NONE;
+ constraints.gridwidth = 1;
+ constraints.gridx = 1;
+ constraints.gridy = 1;
+ constraints.weightx = 0;
+ constraints.weighty = 0;
+ container.add(buttonBar, constraints);
+
+ table.setModel(new EncodingConfigurationTableModel(mediaService
+ .getEncodingConfiguration(), type));
+
+ /*
+ * The first column contains the check boxes which enable/disable their
+ * associated encodings and it doesn't make sense to make it wider than
+ * the check boxes.
+ */
+ TableColumnModel tableColumnModel = table.getColumnModel();
+ TableColumn tableColumn = tableColumnModel.getColumn(0);
+ tableColumn.setMaxWidth(tableColumn.getMinWidth());
+
+ ListSelectionListener tableSelectionListener =
+ new ListSelectionListener()
+ {
+ public void valueChanged(ListSelectionEvent event)
+ {
+ if (table.getSelectedRowCount() == 1)
+ {
+ int selectedRow = table.getSelectedRow();
+ if (selectedRow > -1)
+ {
+ upButton.setEnabled(selectedRow > 0);
+ downButton.setEnabled(selectedRow < (table
+ .getRowCount() - 1));
+ return;
+ }
+ }
+ upButton.setEnabled(false);
+ downButton.setEnabled(false);
+ }
+ };
+ table.getSelectionModel().addListSelectionListener(
+ tableSelectionListener);
+ tableSelectionListener.valueChanged(null);
+
+ ActionListener buttonListener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ Object source = event.getSource();
+ boolean up;
+ if (source == upButton)
+ up = true;
+ else if (source == downButton)
+ up = false;
+ else
+ return;
+
+ move(table, up);
+ }
+ };
+ upButton.addActionListener(buttonListener);
+ downButton.addActionListener(buttonListener);
+
+ return container;
+ }
+
+ private void createPreview(CaptureDeviceInfo device,
+ final Container videoContainer)
+ throws IOException,
+ MediaException
+ {
+ videoContainer.removeAll();
+ if (videoPlayerInPreview != null)
+ disposePlayer(videoPlayerInPreview);
+
+ if (device == null)
+ return;
+
+ DataSource dataSource = Manager.createDataSource(device.getLocator());
+
+ Dimension size = videoContainer.getPreferredSize();
+ VideoMediaStreamImpl
+ .selectVideoSize(dataSource, size.width, size.height);
+
+ Player player = Manager.createPlayer(dataSource);
+
+ videoPlayerInPreview = player;
+
+ player.addControllerListener(new ControllerListener()
+ {
+ public void controllerUpdate(ControllerEvent event)
+ {
+ controllerUpdateForPreview(event, videoContainer);
+ }
+ });
+ player.start();
+ }
+
+ private Component createPreview(int type, final JComboBox comboBox)
+ {
+ final Container preview;
+ if (type == DeviceConfigurationComboBoxModel.VIDEO)
+ {
+ JLabel noPreview
+ = new JLabel(
+ NeomediaActivator
+ .getResources()
+ .getI18NString(
+ "impl.media.configform.NO_PREVIEW"));
+ noPreview.setHorizontalAlignment(SwingConstants.CENTER);
+ noPreview.setVerticalAlignment(SwingConstants.CENTER);
+
+ preview = createVideoContainer(noPreview);
+
+ final ActionListener comboBoxListener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ Object selection = comboBox.getSelectedItem();
+ CaptureDeviceInfo device = null;
+ if (selection
+ instanceof
+ DeviceConfigurationComboBoxModel.CaptureDevice)
+ device
+ = ((DeviceConfigurationComboBoxModel.CaptureDevice)
+ selection)
+ .info;
+
+ if ((device != null) && device.equals(videoDeviceInPreview))
+ return;
+
+ Exception exception;
+ try
+ {
+ createPreview(device, preview);
+ exception = null;
+ }
+ catch (IOException ex)
+ {
+ exception = ex;
+ }
+ catch (MediaException ex)
+ {
+ exception = ex;
+ }
+ if (exception != null)
+ {
+ logger.error(
+ "Failed to create preview for device " + device,
+ exception);
+
+ device = null;
+ }
+
+ videoDeviceInPreview = device;
+ }
+ };
+ comboBox.addActionListener(comboBoxListener);
+
+ /*
+ * We have to initialize the controls to reflect the configuration
+ * at the time of creating this instance. Additionally, because the
+ * video preview will stop when it and its associated controls
+ * become unnecessary, we have to restart it when the mentioned
+ * controls become necessary again. We'll address the two goals
+ * described by pretending there's a selection in the video combo
+ * box when the combo box in question becomes displayable.
+ */
+ comboBox.addHierarchyListener(new HierarchyListener()
+ {
+ public void hierarchyChanged(HierarchyEvent event)
+ {
+ if (((event.getChangeFlags()
+ & HierarchyEvent.DISPLAYABILITY_CHANGED)
+ != 0)
+ && comboBox.isDisplayable())
+ comboBoxListener.actionPerformed(null);
+ }
+ });
+ } else
+ preview = new TransparentPanel();
+ return preview;
+ }
+
+ private Container createVideoContainer(Component noVideoComponent)
+ {
+ return new VideoContainer(noVideoComponent);
+ }
+
+ private void disposePlayer(Player player)
+ {
+ player.stop();
+ player.deallocate();
+ player.close();
+
+ if ((videoPlayerInPreview != null)
+ && videoPlayerInPreview.equals(player))
+ videoPlayerInPreview = null;
+ }
+
+ private char getDisplayedMnemonic(int type)
+ {
+ switch (type)
+ {
+ case DeviceConfigurationComboBoxModel.AUDIO:
+ return
+ NeomediaActivator
+ .getResources()
+ .getI18nMnemonic("impl.media.configform.AUDIO");
+ case DeviceConfigurationComboBoxModel.VIDEO:
+ return
+ NeomediaActivator
+ .getResources()
+ .getI18nMnemonic("impl.media.configform.VIDEO");
+ default:
+ throw new IllegalArgumentException("type");
+ }
+ }
+
+ private String getLabelText(int type)
+ {
+ switch (type)
+ {
+ case DeviceConfigurationComboBoxModel.AUDIO:
+ return
+ NeomediaActivator
+ .getResources()
+ .getI18NString("impl.media.configform.AUDIO");
+ case DeviceConfigurationComboBoxModel.AUDIO_CAPTURE:
+ return
+ NeomediaActivator
+ .getResources()
+ .getI18NString("impl.media.configform.AUDIO_IN");
+ case DeviceConfigurationComboBoxModel.AUDIO_NOTIFY:
+ return
+ NeomediaActivator
+ .getResources()
+ .getI18NString("impl.media.configform.AUDIO_NOTIFY");
+ case DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK:
+ return
+ NeomediaActivator
+ .getResources()
+ .getI18NString("impl.media.configform.AUDIO_OUT");
+ case DeviceConfigurationComboBoxModel.VIDEO:
+ return
+ NeomediaActivator
+ .getResources()
+ .getI18NString("impl.media.configform.VIDEO");
+ default:
+ throw new IllegalArgumentException("type");
+ }
+ }
+
+ private void move(JTable table, boolean up)
+ {
+ int index =
+ ((EncodingConfigurationTableModel) table.getModel()).move(table
+ .getSelectedRow(), up);
+ table.getSelectionModel().setSelectionInterval(index, index);
+ }
+
+ private void showPreview(final Container previewContainer,
+ final Component preview, final Player player)
+ {
+ if (!SwingUtilities.isEventDispatchThread())
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ showPreview(previewContainer, preview, player);
+ }
+ });
+ return;
+ }
+
+ previewContainer.removeAll();
+
+ if (preview != null)
+ {
+ HierarchyListener hierarchyListener = new HierarchyListener()
+ {
+ private Window window;
+
+ private WindowListener windowListener;
+
+ public void dispose()
+ {
+ if (windowListener != null)
+ {
+ if (window != null)
+ {
+ window.removeWindowListener(windowListener);
+ window = null;
+ }
+ windowListener = null;
+ }
+ preview.removeHierarchyListener(this);
+
+ disposePlayer(player);
+ videoDeviceInPreview = null;
+
+ /*
+ * We've just disposed the player which created the preview
+ * component so the preview component is of no use
+ * regardless of whether the Media configuration form will
+ * be redisplayed or not. And since the preview component
+ * appears to be a huge object even after its player is
+ * disposed, make sure to not reference it.
+ */
+ previewContainer.remove(preview);
+ }
+
+ public void hierarchyChanged(HierarchyEvent event)
+ {
+ if ((event.getChangeFlags()
+ & HierarchyEvent.DISPLAYABILITY_CHANGED)
+ != 0)
+ {
+ if (preview.isDisplayable())
+ {
+ if (windowListener == null)
+ {
+ window =
+ SwingUtilities.windowForComponent(preview);
+ if (window != null)
+ {
+ windowListener = new WindowAdapter()
+ {
+ public void windowClosing(
+ WindowEvent event)
+ {
+ dispose();
+ }
+ };
+ window.addWindowListener(windowListener);
+ }
+ }
+ }
+ else
+ {
+ dispose();
+ }
+ }
+ }
+ };
+ preview.addHierarchyListener(hierarchyListener);
+
+ previewContainer.add(preview);
+ }
+ else
+ disposePlayer(player);
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java
index ea4f049b2..e8ea63541 100644
--- a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java
+++ b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java
@@ -10,6 +10,7 @@
import javax.media.*;
+import net.java.sip.communicator.impl.neomedia.codec.*;
import net.java.sip.communicator.impl.neomedia.device.*;
import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.neomedia.device.*;
@@ -21,6 +22,12 @@ public class MediaServiceImpl
implements MediaService
{
+ /**
+ * With this property video support can be disabled (enabled by default).
+ */
+ public static final String DISABLE_VIDEO_SUPPORT_PROPERTY_NAME
+ = "net.java.sip.communicator.service.media.DISABLE_VIDEO_SUPPORT";
+
/**
* The value of the devices property of MediaServiceImpl
* when no MediaDevices are available. Explicitly defined in order
@@ -29,32 +36,11 @@ public class MediaServiceImpl
private static final ListjmfEncoding is null.
+ */
+ public static int jmfToSdpEncoding(String jmfEncoding)
+ {
+ if (jmfEncoding == null)
+ {
+ return UNKNOWN_ENCODING;
+ }
+ else if (jmfEncoding.equals(AudioFormat.ULAW_RTP))
+ {
+ return SdpConstants.PCMU;
+ }
+ else if (jmfEncoding.equals(Constants.ALAW_RTP))
+ {
+ return SdpConstants.PCMA;
+ }
+ else if (jmfEncoding.equals(AudioFormat.GSM_RTP))
+ {
+ return SdpConstants.GSM;
+ }
+ else if (jmfEncoding.equals(AudioFormat.G723_RTP))
+ {
+ return SdpConstants.G723;
+ }
+ else if (jmfEncoding.equals(AudioFormat.DVI_RTP))
+ {
+ return SdpConstants.DVI4_8000;
+ }
+ else if (jmfEncoding.equals(AudioFormat.DVI_RTP))
+ {
+ return SdpConstants.DVI4_16000;
+ }
+ else if (jmfEncoding.equals(AudioFormat.ALAW))
+ {
+ return SdpConstants.PCMA;
+ }
+ else if (jmfEncoding.equals(AudioFormat.G728_RTP))
+ {
+ return SdpConstants.G728;
+ }
+ else if (jmfEncoding.equals(AudioFormat.G729_RTP))
+ {
+ return SdpConstants.G729;
+ }
+ else if (jmfEncoding.equals(VideoFormat.H263_RTP))
+ {
+ return SdpConstants.H263;
+ }
+ else if (jmfEncoding.equals(VideoFormat.JPEG_RTP))
+ {
+ return SdpConstants.JPEG;
+ }
+ else if (jmfEncoding.equals(VideoFormat.H261_RTP))
+ {
+ return SdpConstants.H261;
+ }
+ else if (jmfEncoding.equals(Constants.H264_RTP))
+ {
+ return Constants.H264_RTP_SDP;
+ }
+ else if (jmfEncoding.equals(Constants.ILBC))
+ {
+ return 97;
+ }
+ else if (jmfEncoding.equals(Constants.ILBC_RTP))
+ {
+ return 97;
+ }
+ else if (jmfEncoding.equals(Constants.SPEEX))
+ {
+ return 110;
+ }
+ else if (jmfEncoding.equals(Constants.SPEEX_RTP))
+ {
+ return 110;
+ }
+ else
+ {
+ return UNKNOWN_ENCODING;
+ }
+ }
+
+ /**
+ * Converts the list of sdpEncodings to a list of jmf compatible
+ * encoding strings as specified by the static vars in VideoFormat and
+ * AudioFormat.
+ *
+ * @param sdpEncodings a list containing strings representing SDP format
+ * codes.
+ * @return a list of strings representing JMF compatible encoding names.
+ */
+ public static ListDeviceConfiguration property which
+ * represents the device used by DeviceConfiguration for audio
+ * capture.
+ */
+ public static final String AUDIO_CAPTURE_DEVICE = "AUDIO_CAPTURE_DEVICE";
+
+ /**
+ * The name of the DeviceConfiguration property which
+ * represents the device used by DeviceConfiguration for audio
+ * playback.
+ */
+ public static final String AUDIO_PLAYBACK_DEVICE = "AUDIO_PLAYBACK_DEVICE";
+
+ /**
+ * The name of the DeviceConfiguration property which
+ * represents the device used by DeviceConfiguration for audio
+ * notify.
+ */
+ public static final String AUDIO_NOTIFY_DEVICE = "AUDIO_NOTIFY_DEVICE";
+
+ /**
+ * The name of the DeviceConfiguration property which
+ * represents the device used by DeviceConfiguration for video
+ * capture.
+ */
+ public static final String VIDEO_CAPTURE_DEVICE = "VIDEO_CAPTURE_DEVICE";
+
+ /**
+ * When audio is disabled the selected audio system is with name None.
+ */
+ public static final String AUDIO_SYSTEM_NONE = "None";
+
+ /**
+ * JavaSound sound system.
+ */
+ public static final String AUDIO_SYSTEM_JAVASOUND = "JavaSound";
+
+ /**
+ * PortAudio sound system.
+ */
+ public static final String AUDIO_SYSTEM_PORTAUDIO = "PortAudio";
+
+ private static final String PROP_AUDIO_DEVICE =
+ "net.java.sip.communicator.impl.media.audiodev";
+
+ private static final String PROP_AUDIO_PLAYBACK_DEVICE =
+ "net.java.sip.communicator.impl.media.audio.playbackdev";
+
+ private static final String PROP_AUDIO_NOTIFY_DEVICE =
+ "net.java.sip.communicator.impl.media.audio.notifydev";
+
+ private static final String PROP_AUDIO_DEVICE_IS_DISABLED =
+ "net.java.sip.communicator.impl.media.audiodevIsDisabled";
+
+ private static final String PROP_VIDEO_DEVICE =
+ "net.java.sip.communicator.impl.media.videodev";
+
+ private static final String PROP_VIDEO_DEVICE_IS_DISABLED =
+ "net.java.sip.communicator.impl.media.videodevIsDisabled";
+
+ private static final CaptureDeviceInfo[] NO_CAPTURE_DEVICES =
+ new CaptureDeviceInfo[0];
+
+ private Logger logger = Logger.getLogger(DeviceConfiguration.class);
+
+ /**
+ * The device that we'll be using for audio capture.
+ */
+ private CaptureDeviceInfo audioCaptureDevice = null;
+
+ private CaptureDeviceInfo audioPlaybackDevice = null;
+
+ private CaptureDeviceInfo audioNotifyDevice = null;
+
+ /**
+ * The device that we'll be using for video capture.
+ */
+ private CaptureDeviceInfo videoCaptureDevice;
+
+ private static VectorDeviceConfiguration, amongst which is
+ * {@link #getAudioCaptureDevice()} and represent acceptable values
+ * for {@link #setAudioCaptureDevice(CaptureDeviceInfo)}
+ *
+ * @return an array of CaptureDeviceInfo describing the audio
+ * capture devices available through this
+ * DeviceConfiguration
+ */
+ public CaptureDeviceInfo[] getAvailableAudioCaptureDevices()
+ {
+ VectorDeviceConfiguration, amongst which is
+ * {@link #getAudioCaptureDevice()} and represent acceptable values
+ * for {@link #setAudioCaptureDevice(CaptureDeviceInfo)}
+ *
+ * @param soundSystem
+ * filter capture devices only from the supplied audio system.
+ *
+ * @return an array of CaptureDeviceInfo describing the audio
+ * capture devices available through this
+ * DeviceConfiguration
+ */
+ public CaptureDeviceInfo[] getAvailableAudioCaptureDevices(String soundSystem)
+ {
+ String protocol = null;
+ if(soundSystem.equals(AUDIO_SYSTEM_JAVASOUND))
+ protocol = "javasound";
+ else if(soundSystem.equals(AUDIO_SYSTEM_PORTAUDIO))
+ protocol = "portaudio";
+
+ VectorDeviceConfiguration, amongst which is
+ * {@link #getVideoCaptureDevice()} and represent acceptable values
+ * for {@link #setVideoCaptureDevice(CaptureDeviceInfo)}
+ *
+ * @return an array of CaptureDeviceInfo describing the video
+ * capture devices available through this
+ * DeviceConfiguration
+ */
+ public CaptureDeviceInfo[] getAvailableVideoCaptureDevices()
+ {
+ SetDeviceConfiguration for video capture.
+ *
+ * @param device a CaptureDeviceInfo describing device to be
+ * used by this DeviceConfiguration for video
+ * capture
+ */
+ public void setVideoCaptureDevice(CaptureDeviceInfo device)
+ {
+ if (videoCaptureDevice != device)
+ {
+ CaptureDeviceInfo oldDevice = videoCaptureDevice;
+
+ videoCaptureDevice = device;
+
+ ConfigurationService config
+ = NeomediaActivator.getConfigurationService();
+ config.setProperty(PROP_VIDEO_DEVICE_IS_DISABLED,
+ videoCaptureDevice == null);
+ if (videoCaptureDevice != null)
+ config.setProperty(PROP_VIDEO_DEVICE, videoCaptureDevice
+ .getName());
+
+ firePropertyChange(VIDEO_CAPTURE_DEVICE, oldDevice, device);
+ }
+ }
+
+ /**
+ * Sets the device which is to be used by this
+ * DeviceConfiguration for audio capture.
+ *
+ * @param device a CaptureDeviceInfo describing the device to
+ * be used by this DeviceConfiguration for audio
+ * capture
+ */
+ public void setAudioCaptureDevice(CaptureDeviceInfo device)
+ {
+ if (audioCaptureDevice != device)
+ {
+ CaptureDeviceInfo oldDevice = audioCaptureDevice;
+
+ audioCaptureDevice = device;
+
+ ConfigurationService config
+ = NeomediaActivator.getConfigurationService();
+
+ if (audioCaptureDevice != null)
+ {
+ config.setProperty(PROP_AUDIO_DEVICE, audioCaptureDevice
+ .getName());
+ }
+ else
+ config.setProperty(PROP_AUDIO_DEVICE, null);
+
+ firePropertyChange(AUDIO_CAPTURE_DEVICE, oldDevice, device);
+ }
+ }
+
+ /**
+ * Enable or disable Audio stream transmission.
+ *
+ * @return true if audio capture is supported and false otherwise.
+ */
+ public boolean isAudioCaptureSupported()
+ {
+ return this.audioCaptureDevice != null;
+ }
+
+ /**
+ * Enable or disable Video stream transmission.
+ *
+ * @return true if audio capture is supported and false otherwise.
+ */
+ public boolean isVideoCaptureSupported()
+ {
+ return this.videoCaptureDevice != null;
+ }
+
+ /**
+ * Return the installed Audio Systems.
+ * @return the audio systems names.
+ */
+ public String[] getAvailableAudioSystems()
+ {
+ return audioSystems.toArray(new String[0]);
+ }
+
+ /**
+ * Adds audio system.
+ * @param audioSystemName the name of the audio system.
+ */
+ public static void addAudioSystem(String audioSystemName)
+ {
+ audioSystems.add(audioSystemName);
+ }
+
+ /**
+ * The current selected audio system.
+ * @return the name of the current audio system.
+ */
+ public String getAudioSystem()
+ {
+ return audioSystem;
+ }
+
+ private String getAudioSystem(CaptureDeviceInfo cdi)
+ {
+ String res = null;
+ // Here we iterate over the available audio systems
+ // to be sure that the audio system
+ // is available and enabled on the system we are running on
+ if(cdi.getLocator().getProtocol().equals("javasound"))
+ {
+ IteratorController that this listener is registered with. We use
+ * the event to notify all waiting on our lock and record success or
+ * failure.
+ *
+ * @param ce The event generated.
+ */
+ public void controllerUpdate(ControllerEvent ce)
+ {
+ // If there was an error during configure or
+ // realize, the processor will be closed
+ if (ce instanceof ControllerClosedEvent)
+ {
+ if (ce instanceof ControllerErrorEvent)
+ logger.warn("ControllerErrorEvent: " + ce);
+ else
+ logger.debug("ControllerClosedEvent: " + ce);
+
+ setFailed(true);
+
+ // All controller events, send a notification
+ // to the waiting thread in waitForState method.
+ }
+
+ Object stateLock = getStateLock();
+
+ synchronized (stateLock)
+ {
+ stateLock.notifyAll();
+ }
+ }
+
+ /**
+ * Waits until processor enters state and returns a boolean
+ * indicating success or failure of the operation.
+ *
+ * @param processor Processor
+ * @param state one of the Processor.XXXed state vars
+ * @return true if the state has been reached; false,
+ * otherwise
+ */
+ public synchronized boolean waitForState(Processor processor, int state)
+ {
+ processor.addControllerListener(this);
+ setFailed(false);
+
+ // Call the required method on the processor
+ if (state == Processor.Configured)
+ processor.configure();
+ else if (state == Processor.Realized)
+ processor.realize();
+
+ // Wait until we get an event that confirms the
+ // success of the method, or a failure event.
+ // See StateListener inner class
+ while ((processor.getState() < state) &&!failed)
+ {
+ Object stateLock = getStateLock();
+
+ synchronized (stateLock)
+ {
+ try
+ {
+ stateLock.wait();
+ }
+ catch (InterruptedException ie)
+ {
+ return false;
+ }
+ }
+ }
+
+ return !failed;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/neomedia/format/MediaFormatImpl.java b/src/net/java/sip/communicator/impl/neomedia/format/MediaFormatImpl.java
index 55ef94ac9..b39cec9ab 100644
--- a/src/net/java/sip/communicator/impl/neomedia/format/MediaFormatImpl.java
+++ b/src/net/java/sip/communicator/impl/neomedia/format/MediaFormatImpl.java
@@ -183,6 +183,11 @@ public String getEncoding()
return format.getEncoding();
}
+ public T getFormat()
+ {
+ return format;
+ }
+
/*
* Implements MediaFormat#getFormatParameters(). Returns a copy of the
* format properties of this instance. Modifications to the returned Map do
diff --git a/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf b/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf
new file mode 100644
index 000000000..a2741606c
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf
@@ -0,0 +1,43 @@
+Bundle-Activator: net.java.sip.communicator.impl.neomedia.NeomediaActivator
+Bundle-Name: Neomedia Service Implementation
+Bundle-Description: A bundle that offers Media capture and presentation capabilities.
+Bundle-Vendor: sip-communicator.org
+Bundle-Version: 0.0.1
+System-Bundle: yes
+Import-Package: org.osgi.framework,
+ org.xml.sax,
+ org.bouncycastle.crypto,
+ org.bouncycastle.crypto.digests,
+ org.bouncycastle.crypto.macs,
+ org.bouncycastle.crypto.params,
+ org.bouncycastle.crypto.engines,
+ javax.imageio,
+ javax.sound,
+ javax.sound.sampled,
+ javax.swing,
+ javax.swing.border,
+ javax.swing.event,
+ javax.swing.table,
+ net.java.sip.communicator.service.configuration,
+ net.java.sip.communicator.service.configuration.event,
+ net.java.sip.communicator.service.fileaccess,
+ net.java.sip.communicator.service.gui,
+ net.java.sip.communicator.service.netaddr,
+ net.java.sip.communicator.service.protocol,
+ net.java.sip.communicator.service.protocol.event,
+ net.java.sip.communicator.service.resources,
+ net.java.sip.communicator.util,
+ net.java.sip.communicator.util.swing,
+ quicktime,
+ quicktime.std.sg,
+ quicktime.qd,
+ quicktime.util,
+ quicktime.std.image,
+ gnu.java.zrtp,
+ gnu.java.zrtp.packets,
+ gnu.java.zrtp.utils,
+ gnu.java.zrtp.zidfile
+Export-Package: net.java.sip.communicator.service.neomedia,
+ net.java.sip.communicator.service.neomedia.device,
+ net.java.sip.communicator.service.neomedia.event,
+ net.java.sip.communicator.service.neomedia.format
diff --git a/src/net/java/sip/communicator/service/neomedia/AudioMediaStream.java b/src/net/java/sip/communicator/service/neomedia/AudioMediaStream.java
index 8d9872dc8..ef3a41564 100644
--- a/src/net/java/sip/communicator/service/neomedia/AudioMediaStream.java
+++ b/src/net/java/sip/communicator/service/neomedia/AudioMediaStream.java
@@ -77,9 +77,9 @@ public interface AudioMediaStream
* being fed from this stream's MediaDevice and transmit silence
* instead.
*
- * @param on true if we are to start transmitting silence and
+ * @param mute true if we are to start transmitting silence and
* false if we are to use media from this stream's
* MediaDevice again.
*/
- public void setMute(boolean on);
+ public void setMute(boolean mute);
}
diff --git a/src/net/java/sip/communicator/service/neomedia/DefaultStreamConnector.java b/src/net/java/sip/communicator/service/neomedia/DefaultStreamConnector.java
new file mode 100644
index 000000000..2bf788a48
--- /dev/null
+++ b/src/net/java/sip/communicator/service/neomedia/DefaultStreamConnector.java
@@ -0,0 +1,203 @@
+/*
+ * 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.service.neomedia;
+
+import java.net.*;
+
+import net.java.sip.communicator.impl.neomedia.*;
+import net.java.sip.communicator.service.configuration.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * Represents a default implementation of StreamConnector which is
+ * initialized with a specific pair of control and data DatagramSockets
+ * and which closes them (if they exist) when its {@link #close()} is invoked.
+ *
+ * @author Lubomir Marinov
+ */
+public class DefaultStreamConnector
+ implements StreamConnector
+{
+ private static final Logger logger
+ = Logger.getLogger(DefaultStreamConnector.class);
+
+ /**
+ * The default number of binds that a Media Service Implementation should
+ * execute in case a port is already bound to (each retry would be on a
+ * new random port).
+ */
+ public static final int BIND_RETRIES_DEFAULT_VALUE = 50;
+
+ /**
+ * The name of the property containing the number of binds that a Media
+ * Service Implementation should execute in case a port is already
+ * bound to (each retry would be on a new port in the allowed boundaries).
+ */
+ public static final String BIND_RETRIES_PROPERTY_NAME
+ = "net.java.sip.communicator.service.media.BIND_RETRIES";
+
+ /**
+ * The name of the property that contains the maximum port number that we'd
+ * like our RTP managers to bind upon.
+ */
+ public static final String MAX_PORT_NUMBER_PROPERTY_NAME
+ = "net.java.sip.communicator.service.media.MAX_PORT_NUMBER";
+
+ private static int maxPort = -1;
+
+ /**
+ * The name of the property that contains the minimum port number that we'd
+ * like our RTP managers to bind upon.
+ */
+ public static final String MIN_PORT_NUMBER_PROPERTY_NAME
+ = "net.java.sip.communicator.service.media.MIN_PORT_NUMBER";
+
+ private static int minPort = -1;
+
+ private static synchronized DatagramSocket createDatagramSocket(
+ InetAddress bindAddr)
+ {
+ ConfigurationService config
+ = NeomediaActivator.getConfigurationService();
+ int bindRetries
+ = config
+ .getInt(BIND_RETRIES_PROPERTY_NAME, BIND_RETRIES_DEFAULT_VALUE);
+ if (maxPort < 0)
+ maxPort = config.getInt(MAX_PORT_NUMBER_PROPERTY_NAME, 6000);
+
+ for (int i = 0; i < bindRetries; i++)
+ {
+ if ((minPort < 0) || (minPort > maxPort))
+ minPort = config.getInt(MIN_PORT_NUMBER_PROPERTY_NAME, 5000);
+
+ int port = minPort++;
+
+ try
+ {
+ return new DatagramSocket(port, bindAddr);
+ }
+ catch (SocketException se)
+ {
+ logger
+ .warn(
+ "Retrying a bind because of a failure to bind to address "
+ + bindAddr
+ + " and port "
+ + port,
+ se);
+ }
+ }
+ return null;
+ }
+
+ private final InetAddress bindAddr;
+
+ /**
+ * The DatagramSocket that a stream should use for control data
+ * (e.g. RTCP) traffic.
+ */
+ protected DatagramSocket controlSocket;
+
+ /**
+ * The DatagramSocket that a stream should use for data (e.g. RTP)
+ * traffic.
+ */
+ protected DatagramSocket dataSocket;
+
+ /**
+ * Initializes a new DefaultStreamConnector instance with no
+ * control and data DatagramSockets.
+ * + * Suitable for extenders willing to delay the creation of the control and + * data sockets. For example, they could override + * {@link #getControlSocket()} and/or {@link #getDataSocket()} and create + * them on demand. + */ + public DefaultStreamConnector() + { + this(null, null); + } + + /** + * Initializes a new DefaultStreamConnector instance with a + * specific bind InetAddress. The new instance is to attempt to + * bind on demand to the specified InetAddress in the port range + * defined by the ConfigurationService properties + * {@link #MIN_PORT_NUMBER_PROPERTY_NAME} and + * {@link #MAX_PORT_NUMBER_PROPERTY_NAME} at most + * {@link #BIND_RETRIES_PROPERTY_NAME} times. + * + * @param bindAddr + */ + public DefaultStreamConnector(InetAddress bindAddr) + { + this.bindAddr = bindAddr; + } + + /** + * Initializes a new DefaultStreamConnector instance which is to + * represent a specific pair of control and data DatagramSockets. + * + * @param controlSocket the DatagramSocket to be used for control + * data (e.g. RTCP) traffic + * @param dataSocket the DatagramSocket to be used for data (e.g. + * RTP) traffic + */ + public DefaultStreamConnector( + DatagramSocket controlSocket, + DatagramSocket dataSocket) + { + this.controlSocket = controlSocket; + this.dataSocket = dataSocket; + this.bindAddr = null; + } + + /* + * Implements StreamConnector#close(). + */ + public void close() + { + if (controlSocket != null) + controlSocket.close(); + if (dataSocket != null) + dataSocket.close(); + } + + /* + * Implements StreamConnector#getControlSocket(). + */ + public DatagramSocket getControlSocket() + { + if ((controlSocket == null) && (bindAddr != null)) + controlSocket = createDatagramSocket(bindAddr); + return controlSocket; + } + + /* + * Implements StreamConnector#getDataSocket(). + */ + public DatagramSocket getDataSocket() + { + if ((dataSocket == null) && (bindAddr != null)) + dataSocket = createDatagramSocket(bindAddr); + return dataSocket; + } + + /* + * Implements StreamConnector#started(). Does nothing. + */ + public void started() + { + } + + /* + * Implements StreamConnector#stopped(). Does nothing. + */ + public void stopped() + { + } +} diff --git a/src/net/java/sip/communicator/service/neomedia/MediaStream.java b/src/net/java/sip/communicator/service/neomedia/MediaStream.java index b87783ca5..3fead0b5a 100644 --- a/src/net/java/sip/communicator/service/neomedia/MediaStream.java +++ b/src/net/java/sip/communicator/service/neomedia/MediaStream.java @@ -22,34 +22,6 @@ */ public interface MediaStream { - /** - * The name of the property containing the number of binds that a Media - * Service Implementation should execute in case a port is already - * bound to (each retry would be on a new port in the allowed boundaries). - */ - public static final String BIND_RETRIES_PROPERTY_NAME - = "net.java.sip.communicator.service.media.BIND_RETRIES"; - - /** - * The name of the property that contains the minimum port number that we'd - * like our RTP managers to bind upon. - */ - public static final String MIN_PORT_NUMBER_PROPERTY_NAME - = "net.java.sip.communicator.service.media.MIN_PORT_NUMBER"; - - /** - * The name of the property that contains the maximum port number that we'd - * like our RTP managers to bind upon. - */ - public static final String MAX_PORT_NUMBER_PROPERTY_NAME - = "net.java.sip.communicator.service.media.MAX_PORT_NUMBER"; - - /** - * The default number of binds that a Media Service Implementation should - * execute in case a port is already bound to (each retry would be on a - * new random port). - */ - public static final int BIND_RETRIES_DEFAULT_VALUE = 50; /** * The name of the property which indicates whether the remote SSRC is @@ -82,6 +54,12 @@ public interface MediaStream */ public void stop(); + /** + * Releases the resources allocated by this instance in the course of its + * execution and prepares it to be garbage collected. + */ + public void close(); + /** * Sets the MediaFormat that this MediaStream should transmit in. * @@ -169,4 +147,6 @@ public interface MediaStream * @param listener the listener that we'd like to remove. */ public void removePropertyChangeListener(PropertyChangeListener listener); + + public void setTarget(MediaStreamTarget target); } diff --git a/src/net/java/sip/communicator/service/neomedia/StreamConnector.java b/src/net/java/sip/communicator/service/neomedia/StreamConnector.java index 12496a9fd..2d5bf6e76 100644 --- a/src/net/java/sip/communicator/service/neomedia/StreamConnector.java +++ b/src/net/java/sip/communicator/service/neomedia/StreamConnector.java @@ -38,4 +38,23 @@ public interface StreamConnector * use for control data (e.g. RTCP). */ public DatagramSocket getControlSocket(); + + /** + * Releases the resources allocated by this instance in the course of its + * execution and prepares it to be garbage collected. + */ + public void close(); + + /** + * Notifies this instance that utilization of its DatagramSockets + * for data and/or control traffic has started. + */ + public void started(); + + /** + * Notifies this instance that utilization of its DatagramSockets + * for data and/or control traffic has temporarily stopped. This instance + * should be prepared to be started at a later time again though. + */ + public void stopped(); } diff --git a/src/net/java/sip/communicator/service/neomedia/VideoMediaStream.java b/src/net/java/sip/communicator/service/neomedia/VideoMediaStream.java index 2aa97b64c..57448e60d 100644 --- a/src/net/java/sip/communicator/service/neomedia/VideoMediaStream.java +++ b/src/net/java/sip/communicator/service/neomedia/VideoMediaStream.java @@ -8,7 +8,7 @@ import java.awt.*; -import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.neomedia.event.*; /** * Extends the MediaStream interface and adds methods specific to @@ -37,7 +37,7 @@ public interface VideoMediaStream * * @param listener the VideoListener to be notified when * visual/video Components are being added or removed in this - * CallSession + * VideoMediaStream */ public void addVideoListener(VideoListener listener); }