/** * Checks whether the playback and notification configuration share the same device. * * @return are audio out and notifications using the same device. */ public boolean audioOutAndNotificationsShareSameDevice() { AudioSystem audioSystem = getDeviceConfiguration().getAudioSystem(); CaptureDeviceInfo notify = audioSystem.getSelectedDevice(AudioSystem.DataFlow.NOTIFY); CaptureDeviceInfo playback = audioSystem.getSelectedDevice(AudioSystem.DataFlow.PLAYBACK); if (notify == null) return (playback == null); else { if (playback == null) return false; else return notify.getLocator().equals(playback.getLocator()); } }
/** * Create preview component. * * @param type type * @param comboBox the options. * @param prefSize the preferred size * @return the component. */ private static Component createPreview(int type, final JComboBox comboBox, Dimension prefSize) { JComponent preview = null; if (type == DeviceConfigurationComboBoxModel.AUDIO) { Object selectedItem = comboBox.getSelectedItem(); if (selectedItem instanceof AudioSystem) { AudioSystem audioSystem = (AudioSystem) selectedItem; if (!NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase(audioSystem.getLocatorProtocol())) { preview = new TransparentPanel(new GridBagLayout()); createAudioSystemControls(audioSystem, preview); } } } else 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); preview.setPreferredSize(prefSize); Object selectedItem = comboBox.getSelectedItem(); CaptureDeviceInfo device = null; if (selectedItem instanceof DeviceConfigurationComboBoxModel.CaptureDevice) device = ((DeviceConfigurationComboBoxModel.CaptureDevice) selectedItem).info; Exception exception; try { createVideoPreview(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; } } return preview; }
/** Configures echo cancellation and noise suppression effects. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void configureEffects() { if (!AndroidUtils.hasAPI(16)) return; AudioSystem audioSystem = AudioSystem.getAudioSystem(AudioSystem.LOCATOR_PROTOCOL_AUDIORECORD); // Creates echo canceler if available if (AcousticEchoCanceler.isAvailable()) { AcousticEchoCanceler echoCanceller = AcousticEchoCanceler.create(audioRecord.getAudioSessionId()); if (echoCanceller != null) { echoCanceller.setEnableStatusListener(this); echoCanceller.setEnabled(audioSystem.isEchoCancel()); logger.info("Echo cancellation: " + echoCanceller.getEnabled()); } } // Automatic gain control if (AutomaticGainControl.isAvailable()) { AutomaticGainControl agc = AutomaticGainControl.create(audioRecord.getAudioSessionId()); if (agc != null) { agc.setEnableStatusListener(this); agc.setEnabled(audioSystem.isAutomaticGainControl()); logger.info("Auto gain control: " + agc.getEnabled()); } } // Creates noise suppressor if available if (NoiseSuppressor.isAvailable()) { NoiseSuppressor noiseSuppressor = NoiseSuppressor.create(audioRecord.getAudioSessionId()); if (noiseSuppressor != null) { noiseSuppressor.setEnableStatusListener(this); noiseSuppressor.setEnabled(audioSystem.isDenoise()); logger.info("Noise suppressor: " + noiseSuppressor.getEnabled()); } } }
/** * Creates the UI controls which are to control the details of a specific <tt>AudioSystem</tt>. * * @param audioSystem the <tt>AudioSystem</tt> for which the UI controls to control its details * are to be created * @param container the <tt>JComponent</tt> into which the UI controls which are to control the * details of the specified <tt>audioSystem</tt> are to be added */ public static void createAudioSystemControls(AudioSystem audioSystem, JComponent container) { GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.NORTHWEST; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.weighty = 0; int audioSystemFeatures = audioSystem.getFeatures(); boolean featureNotifyAndPlaybackDevices = ((audioSystemFeatures & AudioSystem.FEATURE_NOTIFY_AND_PLAYBACK_DEVICES) != 0); constraints.gridx = 0; constraints.insets = new Insets(3, 0, 3, 3); constraints.weightx = 0; constraints.gridy = 0; container.add( new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_CAPTURE)), constraints); if (featureNotifyAndPlaybackDevices) { constraints.gridy = 2; container.add( new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK)), constraints); constraints.gridy = 3; container.add( new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_NOTIFY)), constraints); } constraints.gridx = 1; constraints.insets = new Insets(3, 3, 3, 0); constraints.weightx = 1; JComboBox captureCombo = null; if (featureNotifyAndPlaybackDevices) { captureCombo = new JComboBox(); captureCombo.setEditable(false); captureCombo.setModel( new DeviceConfigurationComboBoxModel( captureCombo, mediaService.getDeviceConfiguration(), DeviceConfigurationComboBoxModel.AUDIO_CAPTURE)); constraints.gridy = 0; container.add(captureCombo, constraints); } int anchor = constraints.anchor; SoundLevelIndicator capturePreview = new SoundLevelIndicator( SimpleAudioLevelListener.MIN_LEVEL, SimpleAudioLevelListener.MAX_LEVEL); constraints.anchor = GridBagConstraints.CENTER; constraints.gridy = (captureCombo == null) ? 0 : 1; container.add(capturePreview, constraints); constraints.anchor = anchor; constraints.gridy = GridBagConstraints.RELATIVE; if (featureNotifyAndPlaybackDevices) { JComboBox playbackCombo = new JComboBox(); playbackCombo.setEditable(false); playbackCombo.setModel( new DeviceConfigurationComboBoxModel( captureCombo, mediaService.getDeviceConfiguration(), DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK)); container.add(playbackCombo, constraints); JComboBox notifyCombo = new JComboBox(); notifyCombo.setEditable(false); notifyCombo.setModel( new DeviceConfigurationComboBoxModel( captureCombo, mediaService.getDeviceConfiguration(), DeviceConfigurationComboBoxModel.AUDIO_NOTIFY)); container.add(notifyCombo, constraints); } if ((AudioSystem.FEATURE_ECHO_CANCELLATION & audioSystemFeatures) != 0) { final SIPCommCheckBox echoCancelCheckBox = new SIPCommCheckBox( NeomediaActivator.getResources().getI18NString("impl.media.configform.ECHOCANCEL")); /* * First set the selected one, then add the listener in order to * avoid saving the value when using the default one and only * showing to user without modification. */ echoCancelCheckBox.setSelected(mediaService.getDeviceConfiguration().isEchoCancel()); echoCancelCheckBox.addItemListener( new ItemListener() { public void itemStateChanged(ItemEvent e) { mediaService.getDeviceConfiguration().setEchoCancel(echoCancelCheckBox.isSelected()); } }); container.add(echoCancelCheckBox, constraints); } if ((AudioSystem.FEATURE_DENOISE & audioSystemFeatures) != 0) { final SIPCommCheckBox denoiseCheckBox = new SIPCommCheckBox( NeomediaActivator.getResources().getI18NString("impl.media.configform.DENOISE")); /* * First set the selected one, then add the listener in order to * avoid saving the value when using the default one and only * showing to user without modification. */ denoiseCheckBox.setSelected(mediaService.getDeviceConfiguration().isDenoise()); denoiseCheckBox.addItemListener( new ItemListener() { public void itemStateChanged(ItemEvent e) { mediaService.getDeviceConfiguration().setDenoise(denoiseCheckBox.isSelected()); } }); container.add(denoiseCheckBox, constraints); } createAudioPreview(audioSystem, captureCombo, capturePreview); }
/** * Creates an SCAudioClip from the given URI and adds it to the list of available audio-s. * * @param uri the path where the audio file could be found * @param playback use or not the playback device. * @return a newly created <tt>SCAudioClip</tt> from <tt>uri</tt> */ public SCAudioClip createAudio(String uri, boolean playback) { SCAudioClip audio; synchronized (audiosSyncRoot) { final AudioKey key = new AudioKey(uri, playback); /* * While we want to reuse the SCAudioClip instances, they may be * used by a single user at a time. That's why we'll forget about * them while they are in use and we'll reclaim them when they are * no longer in use. */ audio = (audios == null) ? null : audios.remove(key); if (audio == null) { try { AudioSystem audioSystem = getDeviceConfiguration().getAudioSystem(); if (audioSystem == null) { audio = new JavaSoundClipImpl(uri, this); } else if (NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase( audioSystem.getLocatorProtocol())) { audio = null; } else { audio = new AudioSystemClipImpl(uri, this, audioSystem, playback); } } catch (Throwable t) { if (t instanceof ThreadDeath) throw (ThreadDeath) t; else { /* * Could not initialize a new SCAudioClip instance to be * played. */ return null; } } } /* * Make sure that the SCAudioClip will be reclaimed for reuse when * it is no longer in use. */ if (audio != null) { if (audios == null) audios = new HashMap<AudioKey, SCAudioClip>(); /* * We have to return in the Map which was active at the time the * SCAudioClip was initialized because it may have become * invalid if the playback or notify audio device changed. */ final Map<AudioKey, SCAudioClip> finalAudios = audios; final SCAudioClip finalAudio = audio; audio = new SCAudioClip() { /** * Evaluates a specific <tt>loopCondition</tt> as defined by {@link * SCAudioClip#play(int,Callable)}. * * @param loopCondition the <tt>Callable<Boolean></tt> which represents the * <tt>loopCondition</tt> to be evaluated * @return {@link Boolean#FALSE} if <tt>loopCondition</tt> is <tt>null</tt>; * otherwise, the value returned by invoking {@link Callable#call()} on the * specified <tt>loopCondition</tt> * @throws Exception if the specified <tt>loopCondition</tt> throws an * <tt>Exception</tt> */ private Boolean evaluateLoopCondition(Callable<Boolean> loopCondition) throws Exception { /* * SCAudioClip.play(int,Callable<Boolean>) is * documented to play the SCAudioClip once only if * the loopCondition is null. The same will be * accomplished by returning Boolean.FALSE. */ return (loopCondition == null) ? Boolean.FALSE : loopCondition.call(); } /** * {@inheritDoc} * * <p>Returns the wrapped <tt>SCAudioClip</tt> into the cache from it has earlier been * retrieved in order to allow its reuse. */ @Override protected void finalize() throws Throwable { try { synchronized (audios) { finalAudios.put(key, finalAudio); } } finally { super.finalize(); } } public void play() { /* * SCAudioClip.play() is documented to behave as if * loopInterval is negative and/or loopCondition is * null. We have to take care that this instance * does not get garbage collected until the * finalAudio finishes playing so we will delegate * to this instance's implementation of * SCAudioClip.play(int,Callable<Boolean>) instead * of to the finalAudio's. */ play(-1, null); } public void play(int loopInterval, final Callable<Boolean> finalLoopCondition) { /* * We have to make sure that this instance does not * get garbage collected before the finalAudio * finishes playing. The argument loopCondition of * the method * SCAudioClip.play(int,Callable<Boolean>) will * live/be referenced during that time so we will * use it to hold on to this instance. */ Callable<Boolean> loopCondition = new Callable<Boolean>() { public Boolean call() throws Exception { return evaluateLoopCondition(finalLoopCondition); } }; finalAudio.play(loopInterval, loopCondition); } public void stop() { finalAudio.stop(); } /** * Determines whether this audio is started i.e. a <tt>play</tt> method was invoked * and no subsequent <tt>stop</tt> has been invoked yet. * * @return <tt>true</tt> if this audio is started; otherwise, <tt>false</tt> */ public boolean isStarted() { return finalAudio.isStarted(); } }; } } return audio; }