-
Vincent Lucas authored
Improves device selection via portaudio for MacOSX (hostapi coreaudio): adds the possibility to get the device transport type (USB, bluetooth, etc.) and an unique identifier. Corrects problems when: loading the same device twice (for the moment only for the same hostapi), a new plugged device is selected during a call or if the selected device is unchanged.
Vincent Lucas authoredImproves device selection via portaudio for MacOSX (hostapi coreaudio): adds the possibility to get the device transport type (USB, bluetooth, etc.) and an unique identifier. Corrects problems when: loading the same device twice (for the moment only for the same hostapi), a new plugged device is selected during a call or if the selected device is unchanged.
PulseAudioSystem.java 18.12 KiB
/*
* 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.device;
import java.io.*;
import java.util.*;
import javax.media.*;
import javax.media.format.*;
import org.jitsi.impl.neomedia.*;
import org.jitsi.impl.neomedia.jmfext.media.renderer.audio.*;
import org.jitsi.impl.neomedia.pulseaudio.*;
import org.jitsi.service.version.*;
public class PulseAudioSystem
extends AudioSystem
{
private static final String LOCATOR_PROTOCOL = LOCATOR_PROTOCOL_PULSEAUDIO;
public static final String MEDIA_ROLE_EVENT = "event";
public static final String MEDIA_ROLE_PHONE = "phone";
private static final String NULL_DEV_CAPTURE_DEVICE_INFO_NAME = "Default";
public static void corkStream(long stream, boolean b)
throws IOException
{
if (stream == 0)
throw new IOException("stream");
long o = PA.stream_cork(stream, b, null);
if (o == 0)
throw new IOException("pa_stream_cork");
PA.operation_unref(o);
}
public static PulseAudioSystem getPulseAudioSystem()
{
AudioSystem audioSystem
= AudioSystem.getAudioSystem(PulseAudioSystem.LOCATOR_PROTOCOL);
return
(audioSystem instanceof PulseAudioSystem)
? (PulseAudioSystem) audioSystem
: null;
}
private boolean createContext;
private long context;
private long mainloop;
public PulseAudioSystem()
throws Exception
{
super(LOCATOR_PROTOCOL, FEATURE_NOTIFY_AND_PLAYBACK_DEVICES);
}
private void createContext()
{
if (this.context != 0)
throw new IllegalStateException("context");
startMainloop();
try
{
long proplist = PA.proplist_new();
if (proplist == 0)
throw new RuntimeException("pa_proplist_new");
try
{
populateContextProplist(proplist);
long context
= PA.context_new_with_proplist(
PA.threaded_mainloop_get_api(mainloop),
null /* PA_PROP_APPLICATION_NAME */,
proplist);
if (context == 0)
throw new RuntimeException("pa_context_new_with_proplist");
try
{
if (proplist != 0)
{
PA.proplist_free(proplist);
proplist = 0;
}
Runnable stateCallback
= new Runnable()
{
public void run()
{
signalMainloop(false);
}
};
lockMainloop();
try
{
PA.context_set_state_callback(context, stateCallback);
PA.context_connect(
context,
null,
PA.CONTEXT_NOFLAGS,
0);
try
{
int state
= waitForContextState(
context,
PA.CONTEXT_READY);
if (state != PA.CONTEXT_READY)
throw new IllegalStateException(
"context.state");
this.context = context;
}
finally
{
if (this.context == 0)
PA.context_disconnect(context);
}
}
finally
{
unlockMainloop();
}
}
finally
{
if (this.context == 0)
PA.context_unref(context);
}
}
finally
{
if (proplist != 0)
PA.proplist_free(proplist);
}
}
finally
{
if (this.context == 0)
stopMainloop();
}
}
@Override
public Renderer createRenderer(boolean playback)
{
MediaLocator locator;
if (playback)
locator = null;
else
{
CaptureDeviceInfo notifyDevice
= getDevice(AudioSystem.NOTIFY_INDEX);
if (notifyDevice == null)
return null;
else
locator = notifyDevice.getLocator();
}
PulseAudioRenderer renderer
= new PulseAudioRenderer(
playback ? MEDIA_ROLE_PHONE : MEDIA_ROLE_EVENT);
if ((renderer != null) && (locator != null))
renderer.setLocator(locator);
return renderer;
}
public long createStream(
int sampleRate,
int channels,
String mediaName,
String mediaRole)
throws IllegalStateException,
RuntimeException
{
long context = getContext();
if (context == 0)
throw new IllegalStateException("context");
long sampleSpec
= PA.sample_spec_new(PA.SAMPLE_S16LE, sampleRate, channels);
if (sampleSpec == 0)
throw new RuntimeException("pa_sample_spec_new");
long ret = 0;
try
{
long proplist = PA.proplist_new();
if (proplist == 0)
throw new RuntimeException("pa_proplist_new");
try
{
PA.proplist_sets(proplist, PA.PROP_MEDIA_NAME, mediaRole);
PA.proplist_sets(proplist, PA.PROP_MEDIA_ROLE, mediaRole);
long stream
= PA.stream_new_with_proplist(
context,
null,
sampleSpec,
0,
proplist);
if (stream == 0)
throw new RuntimeException(
"pa_stream_new_with_proplist");
try
{
ret = stream;
}
finally
{
if (ret == 0)
PA.stream_unref(stream);
}
}
finally
{
if (proplist != 0)
PA.proplist_free(proplist);
}
}
finally
{
if (sampleSpec != 0)
PA.sample_spec_free(sampleSpec);
}
return ret;
}
protected synchronized void doInitialize()
throws Exception
{
long context = getContext();
final List<ExtendedCaptureDeviceInfo> captureDevices
= new LinkedList<ExtendedCaptureDeviceInfo>();
final List<Format> captureDeviceFormats = new LinkedList<Format>();
PA.source_info_cb_t sourceInfoListCallback
= new PA.source_info_cb_t()
{
public void callback(long c, long i, int eol)
{
try
{
if ((eol == 0) && (i != 0))
{
sourceInfoListCallback(
c,
i,
captureDevices,
captureDeviceFormats);
}
}
finally
{
signalMainloop(false);
}
}
};
final List<ExtendedCaptureDeviceInfo> playbackDevices
= new LinkedList<ExtendedCaptureDeviceInfo>();
final List<Format> playbackDeviceFormats = new LinkedList<Format>();
PA.sink_info_cb_t sinkInfoListCallback
= new PA.sink_info_cb_t()
{
public void callback(long c, long i, int eol)
{
try
{
if ((eol == 0) && (i != 0))
{
sinkInfoListCallback(
c,
i,
playbackDevices,
playbackDeviceFormats);
}
}
finally
{
signalMainloop(false);
}
}
};
lockMainloop();
try
{
long o
= PA.context_get_source_info_list(
context,
sourceInfoListCallback);
if (o == 0)
throw new RuntimeException("pa_context_get_source_info_list");
try
{
while (PA.operation_get_state(o) == PA.OPERATION_RUNNING)
waitMainloop();
}
finally
{
PA.operation_unref(o);
}
o
= PA.context_get_sink_info_list(
context,
sinkInfoListCallback);
if (o == 0)
throw new RuntimeException("pa_context_get_sink_info_list");
try
{
while (PA.operation_get_state(o) == PA.OPERATION_RUNNING)
waitMainloop();
}
finally
{
PA.operation_unref(o);
}
}
finally
{
unlockMainloop();
}
if (!captureDeviceFormats.isEmpty())
{
captureDevices.add(
0,
new ExtendedCaptureDeviceInfo(
NULL_DEV_CAPTURE_DEVICE_INFO_NAME,
new MediaLocator(LOCATOR_PROTOCOL + ":"),
captureDeviceFormats.toArray(
new Format[captureDeviceFormats.size()]),
null,
null));
}
if (!playbackDevices.isEmpty())
{
playbackDevices.add(
0,
new ExtendedCaptureDeviceInfo(
NULL_DEV_CAPTURE_DEVICE_INFO_NAME,
new MediaLocator(LOCATOR_PROTOCOL + ":"),
null,
null,
null));
}
setCaptureDevices(captureDevices);
setPlaybackDevices(playbackDevices);
}
public synchronized long getContext()
{
if (context == 0)
{
if (!createContext)
{
createContext = true;
createContext();
}
if (context == 0)
throw new IllegalStateException("context");
}
return context;
}
public void lockMainloop()
{
PA.threaded_mainloop_lock(mainloop);
}
/**
* Populates a specific <tt>pa_proplist</tt> which is to be used with a
* <tt>pa_context</tt> with properties such as the application name and
* version.
*
* @param proplist the <tt>pa_proplist</tt> which is to be populated with
* <tt>pa_context</tt>-related properties such as the application name and
* version
*/
private void populateContextProplist(long proplist)
{
/*
* XXX For the sake of simplicity while working on libjitsi, get the
* version information in the form of System property values instead of
* going through the VersionService.
*/
String applicationName
= System.getProperty(Version.PNAME_APPLICATION_NAME);
if (applicationName != null)
PA.proplist_sets(
proplist,
PA.PROP_APPLICATION_NAME,
applicationName);
String applicationVersion
= System.getProperty(Version.PNAME_APPLICATION_VERSION);
if (applicationVersion != null)
PA.proplist_sets(
proplist,
PA.PROP_APPLICATION_VERSION,
applicationVersion);
}
public void signalMainloop(boolean waitForAccept)
{
PA.threaded_mainloop_signal(mainloop, waitForAccept);
}
private void sinkInfoListCallback(
long context,
long sinkInfo,
List<ExtendedCaptureDeviceInfo> deviceList,
List<Format> formatList)
{
int sampleSpecFormat = PA.sink_info_get_sample_spec_format(sinkInfo);
if (sampleSpecFormat != PA.SAMPLE_S16LE)
return;
String description = PA.sink_info_get_description(sinkInfo);
String name = PA.sink_info_get_name(sinkInfo);
if (description == null)
description = name;
deviceList.add(
new ExtendedCaptureDeviceInfo(
description,
new MediaLocator(
LOCATOR_PROTOCOL
+ ":"
+ name),
null,
null,
null));
}
private void sourceInfoListCallback(
long context,
long sourceInfo,
List<ExtendedCaptureDeviceInfo> deviceList,
List<Format> formatList)
{
int monitorOfSink = PA.source_info_get_monitor_of_sink(sourceInfo);
if (monitorOfSink != PA.INVALID_INDEX)
return;
int sampleSpecFormat
= PA.source_info_get_sample_spec_format(sourceInfo);
if (sampleSpecFormat != PA.SAMPLE_S16LE)
return;
int channels = PA.source_info_get_sample_spec_channels(sourceInfo);
int rate = PA.source_info_get_sample_spec_rate(sourceInfo);
List<Format> sourceInfoFormatList = new LinkedList<Format>();
if ((MediaUtils.MAX_AUDIO_CHANNELS != Format.NOT_SPECIFIED)
&& (MediaUtils.MAX_AUDIO_CHANNELS < channels))
channels = MediaUtils.MAX_AUDIO_CHANNELS;
if ((MediaUtils.MAX_AUDIO_SAMPLE_RATE != Format.NOT_SPECIFIED)
&& (MediaUtils.MAX_AUDIO_SAMPLE_RATE < rate))
rate = (int) MediaUtils.MAX_AUDIO_SAMPLE_RATE;
AudioFormat audioFormat
= new AudioFormat(
AudioFormat.LINEAR,
rate,
16,
channels,
AudioFormat.LITTLE_ENDIAN,
AudioFormat.SIGNED,
Format.NOT_SPECIFIED /* frameSizeInBits */,
Format.NOT_SPECIFIED /* frameRate */,
Format.byteArray);
if (!sourceInfoFormatList.contains(audioFormat))
{
sourceInfoFormatList.add(audioFormat);
if (!formatList.contains(audioFormat))
formatList.add(audioFormat);
}
if (!formatList.isEmpty())
{
String description = PA.source_info_get_description(sourceInfo);
String name = PA.source_info_get_name(sourceInfo);
if (description == null)
description = name;
deviceList.add(
new ExtendedCaptureDeviceInfo(
description,
new MediaLocator(
LOCATOR_PROTOCOL
+ ":"
+ name),
sourceInfoFormatList.toArray(
new Format[sourceInfoFormatList.size()]),
null,
null));
}
}
private void startMainloop()
{
if (this.mainloop != 0)
throw new IllegalStateException("mainloop");
long mainloop = PA.threaded_mainloop_new();
if (mainloop == 0)
throw new RuntimeException("pa_threaded_mainloop_new");
try
{
if (PA.threaded_mainloop_start(mainloop) < 0)
throw new RuntimeException("pa_threaded_mainloop_start");
this.mainloop = mainloop;
}
finally
{
if (this.mainloop == 0)
PA.threaded_mainloop_free(mainloop);
}
}
private void stopMainloop()
{
if (this.mainloop == 0)
throw new IllegalStateException("mainloop");
long mainloop = this.mainloop;
this.mainloop = 0;
PA.threaded_mainloop_stop(mainloop);
PA.threaded_mainloop_free(mainloop);
}
@Override
public String toString()
{
return "PulseAudio";
}
public void unlockMainloop()
{
PA.threaded_mainloop_unlock(mainloop);
}
private int waitForContextState(
long context,
int stateToWaitFor)
{
int state;
while (true)
{
state = PA.context_get_state(context);
if ((PA.CONTEXT_FAILED == state)
|| (stateToWaitFor == state)
|| (PA.CONTEXT_TERMINATED == state))
break;
waitMainloop();
}
return state;
}
public int waitForStreamState(
long stream,
int stateToWaitFor)
{
int state;
while (true)
{
state = PA.stream_get_state(stream);
if ((stateToWaitFor == state)
|| (PA.STREAM_FAILED == state)
|| (PA.STREAM_TERMINATED == state))
break;
waitMainloop();
}
return state;
}
public void waitMainloop()
{
PA.threaded_mainloop_wait(mainloop);
}
}