quisk-kc4upr/sound_pulseaudio.c

1008 lines
33 KiB
C
Raw Normal View History

/*
* sound_pulseaudio.c is part of Quisk, and is Copyright the following
* authors:
*
* Philip G. Lee <rocketman768@gmail.com>, 2014
* Jim Ahlstrom, N2ADR, October, 2014
* Eric Thornton, KM4DSJ, September, 2015
*
* This code replaces the pulseaudio-simple version by Philip G. Lee. It
* uses the asynchronous pulseaudio API. It was written by Eric Thornton, 2015.
*
* Quisk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* Quisk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* This pulseaudio interface was rewritten to utilize the features in the
* asynchronous API and threaded mainloop.
* Eric Thornton, KM4DSJ 2015
*/
/*
* 2017 Nov by N2ADR Remove most exit(1) statements.
* 2017 Nov by N2ADR Add code to enable PulseAudio to continue with mis-spelled device names.
*/
#include <Python.h>
#include <stdio.h>
#include <string.h>
#include <complex.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include "quisk.h"
#include <pulse/pulseaudio.h>
// From pulsecore/macro.h
#define pa_memzero(x,l) (memset((x), 0, (l)))
#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))
// Current sound status
extern struct sound_conf quisk_sound_state;
//pointers for aychronous threaded loop
static pa_threaded_mainloop *pa_ml;
static pa_mainloop_api *pa_mlapi;
static pa_context *pa_ctx; //local context
static pa_context *pa_IQ_ctx; //remote context for IQ audio
volatile int streams_ready = 0; //This is ++/-- by the mainloop thread
volatile int streams_to_start;
// remember all open devices for easy cleanup on exit
static pa_stream *OpenPulseDevices[PA_LIST_SIZE * 2] = {NULL};
static int have_QuiskDigitalInput; // Do we need to create this null sink?
static int have_QuiskDigitalOutput; // Do we need to create this null sink?
/* This callback happens any time a stream changes state. Here, it's primary used to
* tell the quisk thread when streams are ready.
*/
void stream_state_callback(pa_stream *s, void *userdata) {
struct sound_dev *dev = userdata;
assert(s);
assert(dev);
dev->pulse_stream_state = pa_stream_get_state(s);
switch (dev->pulse_stream_state) {
case PA_STREAM_CREATING:
if (quisk_sound_state.verbose_pulse)
printf("\n**Stream %s state Creating\n", dev->name);
break;
case PA_STREAM_TERMINATED:
if (quisk_sound_state.verbose_pulse)
printf("\n**Stream %s state Terminated\n", dev->name);
streams_ready--;
break;
case PA_STREAM_READY:
if (quisk_sound_state.verbose_pulse)
printf("\n**Stream %s state Ready\n", dev->name);
streams_ready++; //increment counter to tell other thread that this stream is ready
streams_to_start++;
if (quisk_sound_state.verbose_pulse) {
const pa_buffer_attr *a;
printf("Connected to device %s (%u, %ssuspended). ",
pa_stream_get_device_name(s), pa_stream_get_device_index(s),
pa_stream_is_suspended(s) ? "" : "not ");
if (!(a = pa_stream_get_buffer_attr(s)))
printf("pa_stream_get_buffer_attr() failed: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
else if (!(a->prebuf)) {
printf("Buffer metrics %s: maxlength=%u, fragsize=%u\n", dev->name, a->maxlength, a->fragsize);
}
else {
printf("Buffer metrics %s: maxlength=%u, prebuf=%u, tlength=%u minreq=%u\n",
dev->name, a->maxlength, a->prebuf, a->tlength, a->minreq);
}
}
break;
case PA_STREAM_FAILED:
default:
snprintf(quisk_sound_state.err_msg, QUISK_SC_SIZE,
"Stream error: %.40s - %.40s", dev->name, pa_strerror(pa_context_errno(pa_stream_get_context(s))));
if (quisk_sound_state.verbose_pulse)
printf("\n**Stream %s state Failed\n", dev->name);
printf("%s\n", quisk_sound_state.err_msg);
streams_to_start++;
break;
}
}
//Indicates underflow on passed stream (playback)
static void stream_underflow_callback(pa_stream *s, void *userdata) {
struct sound_dev *dev = userdata;
assert(s);
assert(dev);
if (quisk_sound_state.verbose_pulse)
printf("Stream underrun %s\n", dev->name);
dev->dev_error++;
}
//Indicates overflow on passed stream (record)
static void stream_overflow_callback(pa_stream *s, void *userdata) {
struct sound_dev *dev = userdata;
assert(s);
if (quisk_sound_state.verbose_pulse)
printf("Stream overrun %s\n", dev->name);
dev->dev_error++;
}
//Indicates stream has started
static void stream_started_callback(pa_stream *s, void *userdata) {
struct sound_dev *dev = userdata;
assert(s);
assert(dev);
if (quisk_sound_state.verbose_pulse)
printf("Stream started %s\n", dev->name);
}
//Called when cork/uncork has succeeded on passed stream. Signals mainloop when complete.
static void stream_corked_callback(pa_stream *s, int success, void *userdata) {
assert(s);
struct sound_dev *dev = userdata;
if (s) {
if (quisk_sound_state.verbose_pulse)
printf("Stream cork/uncork %s success\n", dev->name);
pa_threaded_mainloop_signal(pa_ml, 0);
}
else {
if (quisk_sound_state.verbose_pulse)
printf("Stream cork/uncork %s Failure!\n", dev->name);
exit(11);
}
}
// Called when stream flush has completed.
static void stream_flushed_callback(pa_stream *s, int success, void *userdata) {
assert(s);
struct sound_dev *dev = userdata;
if (s) {
printf("Stream flush %s success\n", dev->name);
pa_threaded_mainloop_signal(pa_ml, 0);
}
else {
printf("Stream flush %s Failure!\n", dev->name);
exit(12);
}
}
// This is called by the play function when the timing structure is updated.
static void stream_timing_callback(pa_stream *s, int success, void *userdata) {
struct sound_dev *dev = userdata;
pa_usec_t l;
int negative = 0;
assert(s);
if (!success || pa_stream_get_latency(s, &l, &negative) < 0) {
printf("pa_stream_get_latency() failed: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
return;
}
dev->dev_latency = (int)l;
if (negative)
dev->dev_latency *= -1;
pa_threaded_mainloop_signal(pa_ml, 0);
}
/* This callback allows us to read in the server side stream information so that we
* can match stream formats and sizes to what is configured in pulseaudio.
*/
static void server_info_cb(pa_context *c, const pa_server_info *info, void *userdata) {
struct sound_dev **pDevices = userdata;
pa_buffer_attr rec_ba;
pa_buffer_attr play_ba;
pa_sample_spec ss;
pa_sample_spec default_ss;
pa_stream_flags_t pb_flags = PA_STREAM_NOFLAGS;
pa_stream_flags_t rec_flags = PA_STREAM_ADJUST_LATENCY;
default_ss = info->sample_spec;
printf("Connected to %s \n", info->host_name);
while(*pDevices) {
struct sound_dev *dev = *pDevices++;
const char *dev_name;
pa_stream *s;
pa_zero(rec_ba);
pa_zero(play_ba);
if (dev->name[5] == ':')
dev_name = dev->name + 6; // the device name is given; "pulse:alsa_input.pci-0000_00_1b.0.analog-stereo"
else
dev_name = NULL; // the device name is "pulse" for the default device
if (quisk_sound_state.verbose_pulse)
printf("Opening Device %s\n", dev_name);
//Construct sample specification. Use S16LE if availiable. Default to Float32 for others.
//If the source/sink is not Float32, Pulseaudio will convert it (uses CPU)
//dev->sample_bytes = (int)pa_frame_size(&ss) / ss.channels;
if (default_ss.format == PA_SAMPLE_S16LE) {
dev->sample_bytes = 2;
ss.format = default_ss.format;
}
else {
dev->sample_bytes = 4;
ss.format = PA_SAMPLE_FLOAT32LE;
}
ss.rate = dev->sample_rate;
ss.channels = dev->num_channels;
rec_ba.maxlength = (uint32_t) -1;
rec_ba.fragsize = (uint32_t) SAMP_BUFFER_SIZE / 16; //higher numbers eat cpu on reading monitor streams.
play_ba.maxlength = (uint32_t) -1;
play_ba.prebuf = (uint32_t) (dev->sample_bytes * ss.channels * dev->latency_frames);
//play_ba.tlength = (uint32_t) -1;
play_ba.tlength = play_ba.prebuf;
if (dev->latency_frames == 0)
play_ba.minreq = (uint32_t) 0; //verify this is sane
else
play_ba.minreq = (uint32_t) -1;
if (dev->stream_dir_record) {
if (!(s = pa_stream_new(c, dev->stream_description, &ss, NULL))) {
printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c)));
continue;
}
if (pa_stream_connect_record(s, dev_name, &rec_ba, rec_flags) < 0) {
printf("pa_stream_connect_record() failed: %s", pa_strerror(pa_context_errno(c)));
continue;
}
pa_stream_set_overflow_callback(s, stream_overflow_callback, dev);
}
else {
pa_cvolume cv;
pa_volume_t volume = PA_VOLUME_NORM;
if (!(s = pa_stream_new(c, dev->stream_description, &ss, NULL))) {
printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c)));
continue;
}
if (pa_stream_connect_playback(s, dev_name, &play_ba, pb_flags, pa_cvolume_set(&cv, ss.channels, volume), NULL) < 0) {
printf("pa_stream_connect_playback() failed: %s", pa_strerror(pa_context_errno(c)));
continue;
}
pa_stream_set_underflow_callback(s, stream_underflow_callback, dev);
}
pa_stream_set_state_callback(s, stream_state_callback, dev);
pa_stream_set_started_callback(s, stream_started_callback, dev);
dev->handle = (void*)s; //save memory address for stream in handle
int i;
for(i=0;i < PA_LIST_SIZE;i++) { //save address for stream for easy exit
if (!(OpenPulseDevices[i])) {
OpenPulseDevices[i] = dev->handle;
break;
}
}
}
}
//Context state callback. Basically here to pass initialization to server_info_cb
void state_cb(pa_context *c, void *userdata) {
pa_context_state_t state;
state = pa_context_get_state(c);
switch (state) {
// There are just here for reference
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
default:
break;
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
printf("Context Terminated\n");
break;
case PA_CONTEXT_READY: {
pa_operation *o;
if (!(o = pa_context_get_server_info(c, server_info_cb, userdata)))
printf("pa_context_get_server_info() failed: %s", pa_strerror(pa_context_errno(c)));
else
pa_operation_unref(o);
}
}
}
#if 0
/* Stream draining complete */
static void stream_drain_complete(pa_stream*s, int success, void *userdata) {
struct sound_dev *dev = userdata;
if (!success) {
printf("Failed to drain stream: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
quit(1);
}
if (quisk_sound_state.verbose_pulse)
printf("Playback stream %s drained.\n", dev->name);
}
/*drain stream function*/
void quisk_drain_cork_stream(struct sound_dev *dev) {
pa_stream *s = dev->handle;
pa_operation *o;
if (!(o = pa_stream_drain(s, stream_drain_complete, NULL))) {
printf("pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
exit(1);
}
pa_operation_unref(o);
}
#endif
//Cork function. Holds mainloop lock until operation is completed.
void quisk_cork_pulseaudio(struct sound_dev *dev, int b) {
pa_stream *s = dev->handle;
pa_operation *o;
pa_threaded_mainloop_lock(pa_ml);
if (!(o = pa_stream_cork(s, b, stream_corked_callback, dev))) {
printf("pa_stream_cork(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
exit(13);
}
else {
while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(pa_ml);
pa_operation_unref(o);
}
pa_threaded_mainloop_unlock(pa_ml);
if (b)
dev->cork_status = 1;
else
dev->cork_status = 0;
}
//Flush function. Holds mainloop lock until operation is completed.
void quisk_flush_pulseaudio(struct sound_dev *dev) {
pa_stream *s = dev->handle;
pa_operation *o;
pa_threaded_mainloop_lock(pa_ml);
if (!(o = pa_stream_flush(s, stream_flushed_callback, dev))) {
printf("pa_stream_flush(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
exit(14);
}
else {
while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(pa_ml);
pa_operation_unref(o);
}
pa_threaded_mainloop_unlock(pa_ml);
}
static void WaitForPoll(void) { // Implement a blocking read
static double time0 = 0; // start time in seconds
double timer; // time remaining from last poll usec
timer = quisk_sound_state.data_poll_usec - (QuiskTimeSec() - time0) * 1e6;
if (timer > 1000.0) // see if enough time has elapsed
QuiskSleepMicrosec((int)timer); // wait for the remainder of the poll interval
time0 = QuiskTimeSec(); // reset starting time value
}
/* Read samples from the PulseAudio device.
* Samples are converted to complex form based upon format, counted
* and returned via cSamples pointer.
* Returns the number of samples placed into cSamples
*/
int quisk_read_pulseaudio(struct sound_dev *dev, complex double *cSamples)
{ // cSamples can be NULL to discard samples
int i, nSamples;
int read_frames; // A frame is a sample from each channel
const void * fbuffer;
pa_stream *s = dev->handle;
size_t read_bytes;
if (!dev || dev->pulse_stream_state != PA_STREAM_READY)
return 0;
if (dev->cork_status) {
if (dev->read_frames != 0) {
WaitForPoll();
}
return 0;
}
// Note: Access to PulseAudio data from our sound thread requires locking the threaded mainloop.
if (dev->read_frames == 0) { // non-blocking: read available frames
pa_threaded_mainloop_lock(pa_ml);
read_frames = pa_stream_readable_size(s) / dev->num_channels / dev->sample_bytes;
if (read_frames == 0) {
pa_threaded_mainloop_unlock(pa_ml);
return 0;
}
dev->dev_latency = read_frames * dev->num_channels * 1000 / (dev->sample_rate / 1000);
}
else { // Blocking read for dev->read_frames total frames
WaitForPoll();
pa_threaded_mainloop_lock(pa_ml);
read_frames = pa_stream_readable_size(s) / dev->num_channels / dev->sample_bytes;
if (read_frames == 0) {
pa_threaded_mainloop_unlock(pa_ml);
return 0;
}
dev->dev_latency = read_frames * dev->num_channels * 1000 / (dev->sample_rate / 1000);
}
nSamples = 0;
while (nSamples < read_frames) { // read samples in chunks until we have enough samples
if (pa_stream_peek (s, &fbuffer, &read_bytes) < 0) {
printf("Failure of pa_stream_peek in quisk_read_pulseaudio\n");
pa_threaded_mainloop_unlock(pa_ml);
return nSamples;
}
if (fbuffer == NULL && read_bytes == 0) { // buffer is empty
break;
}
if (fbuffer == NULL) { // there is a hole in the buffer
pa_stream_drop(s);
continue;
}
if (nSamples * dev->num_channels * dev->sample_bytes + read_bytes >= SAMP_BUFFER_SIZE * 8 / 10) {
if (quisk_sound_state.verbose_pulse)
printf("buffer full on %s\n", dev->name);
pa_stream_drop(s); // limit read request to buffer size
break;
}
// Convert sampled data to complex data. dev->num_channels must be 2.
if (dev->sample_bytes == 4) { //float32
float fi, fq;
for( i = 0; i < read_bytes; i += (dev->num_channels * 4)) {
memcpy(&fi, fbuffer + i + (dev->channel_I * 4), 4);
memcpy(&fq, fbuffer + i + (dev->channel_Q * 4), 4);
if (fi >= 1.0 || fi <= -1.0)
dev->overrange++;
if (fq >= 1.0 || fq <= -1.0)
dev->overrange++;
if (cSamples)
cSamples[nSamples] = (fi + I * fq) * CLIP32;
nSamples++;
}
}
else if (dev->sample_bytes == 2) { //16bit integer little-endian
int16_t si, sq;
for( i = 0; i < read_bytes; i += (dev->num_channels * 2)) {
memcpy(&si, fbuffer + i + (dev->channel_I * 2), 2);
memcpy(&sq, fbuffer + i + (dev->channel_Q * 2), 2);
if (si >= CLIP16 || si <= -CLIP16)
dev->overrange++;
if (sq >= CLIP16 || sq <= -CLIP16)
dev->overrange++;
int ii = si << 16;
int qq = sq << 16;
if (cSamples)
cSamples[nSamples] = ii + I * qq;
nSamples++;
}
}
else {
printf("Unknown sample size for %s", dev->name);
}
pa_stream_drop(s);
}
pa_threaded_mainloop_unlock(pa_ml);
return nSamples;
}
/*!
* \Write outgoing samples directly to pulseaudio server.
* \param playdev Input. Device to which to play the samples.
* \param nSamples Input. Number of samples to play.
* \param cSamples Input. Sample buffer to play from.
* \param report_latency Input. 1 to update \c quisk_sound_state.latencyPlay, 0 otherwise.
* \param volume Input. Ratio in [0,1] by which to scale the played samples.
*/
void quisk_play_pulseaudio(struct sound_dev *dev, int nSamples, complex double *cSamples,
int report_latency, double volume) {
pa_stream *s = dev->handle;
int i=0, n=0;
void *fbuffer;
int fbuffer_bytes = 0;
if( !dev || nSamples <= 0 || dev->pulse_stream_state != PA_STREAM_READY)
return;
if (dev->cork_status)
return;
if (report_latency) { // Report the latency, if requested.
pa_operation *o;
pa_threaded_mainloop_lock(pa_ml);
if (!(o = pa_stream_update_timing_info(s, stream_timing_callback, dev))) {
printf("pa_stream_update_timing(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
}
else {
while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(pa_ml);
pa_operation_unref(o);
}
pa_threaded_mainloop_unlock(pa_ml);
}
fbuffer = pa_xmalloc(nSamples * dev->num_channels * dev->sample_bytes);
// Convert from complex data to framebuffer
if (dev->sample_bytes == 4) {
float fi=0.f, fq=0.f;
for(i = 0, n = 0; n < nSamples; i += (dev->num_channels * 4), ++n) {
fi = (volume * creal(cSamples[n])) / CLIP32;
fq = (volume * cimag(cSamples[n])) / CLIP32;
memcpy(fbuffer + i + (dev->channel_I * 4), &fi, 4);
memcpy(fbuffer + i + (dev->channel_Q * 4), &fq, 4);
}
}
else if (dev->sample_bytes == 2) {
int ii, qq;
for(i = 0, n = 0; n < nSamples; i += (dev->num_channels * 2), ++n) {
ii = (int)(volume * creal(cSamples[n]) / 65536);
qq = (int)(volume * cimag(cSamples[n]) / 65536);
memcpy(fbuffer + i + (dev->channel_I * 2), &ii, 2);
memcpy(fbuffer + i + (dev->channel_Q * 2), &qq, 2);
}
}
else {
printf("Unknown sample size for %s", dev->name);
exit(15);
}
fbuffer_bytes = nSamples * dev->num_channels * dev->sample_bytes;
pa_threaded_mainloop_lock(pa_ml);
size_t writable = pa_stream_writable_size(s);
if (writable > 0) {
if ( writable > 1024*1000 ) //sanity check to prevent pa_xmalloc from crashing on monitor streams
writable = 1024*1000;
if (fbuffer_bytes > writable) {
if (quisk_sound_state.verbose_pulse)
printf("Truncating write by %u bytes\n", fbuffer_bytes - (int)writable);
fbuffer_bytes = writable;
}
pa_stream_write(dev->handle, fbuffer, (size_t)fbuffer_bytes, NULL, 0, PA_SEEK_RELATIVE);
//printf("wrote %d to %s\n", writable, dev->name);
}
else {
if (quisk_sound_state.verbose_pulse)
printf("Can't write to stream %s. Dropping %d bytes\n", dev->name, fbuffer_bytes);
}
pa_threaded_mainloop_unlock(pa_ml);
pa_xfree(fbuffer);
fbuffer=NULL;
}
//This is a function to sort the device list into local and remote lists.
void sort_devices(struct sound_dev **plist, struct sound_dev **pLocal, struct sound_dev **pRemote) {
while(*plist) {
struct sound_dev *dev = *plist++;
dev->pulse_stream_state = PA_STREAM_UNCONNECTED;
// Not a PulseAudio device
if( dev->driver != DEV_DRIVER_PULSEAUDIO )
continue;
// Device without name: sad.
if( !dev->name[0] )
continue;
// This is a remote device
if(dev->server[0]) {
int i;
for(i=0;i < PA_LIST_SIZE;i++) {
if (!(*(pRemote+i))) {
*(pRemote+i) = dev;
break;
}
}
}
// This is a local device
else {
int i;
for(i=0;i < PA_LIST_SIZE; i++) {
if (!(*(pLocal+i))) {
*(pLocal+i) = dev;
break;
}
}
}
}
}
/*!
* \brief Search for and open PulseAudio devices.
*
* \param pCapture Input/Output. Array of capture devices to search through.
* If a device has its \c sound_dev.driver field set to
* \c DEV_DRIVER_PULSEAUDIO, it will be opened for recording.
* \param pPlayback Input/Output. Array of playback devices to search through.
* If a device has its \c sound_dev.driver field set to
* \c DEV_DRIVER_PULSEAUDIO, it will be opened for recording.
*/
//sound_dev ** pointer(address) for list of addresses
//sound_dev * fpointer(address) for list of addresses
void quisk_start_sound_pulseaudio(struct sound_dev **pCapture, struct sound_dev **pPlayback) {
int num_pa_devices = 0;
int i;
//sorted lists of local and remote devices
struct sound_dev *LocalPulseDevices[PA_LIST_SIZE] = {NULL};
struct sound_dev *RemotePulseDevices[PA_LIST_SIZE] = {NULL};
sort_devices(pCapture, LocalPulseDevices, RemotePulseDevices);
sort_devices(pPlayback, LocalPulseDevices, RemotePulseDevices);
pa_IQ_ctx = NULL;
pa_ctx = NULL;
pa_ml = NULL;
pa_mlapi = NULL;
streams_to_start = 0;
//quisk_sound_state.verbose_pulse = 1;
if (!RemotePulseDevices[0] && !LocalPulseDevices[0]) {
if (quisk_sound_state.verbose_pulse)
printf("No pulseaudio devices to open!\n");
return; //nothing to open. No need to start the mainloop.
}
// Create a mainloop API
pa_ml = pa_threaded_mainloop_new();
pa_mlapi = pa_threaded_mainloop_get_api(pa_ml);
assert(pa_signal_init(pa_mlapi) == 0);
if (pa_threaded_mainloop_start(pa_ml) < 0) {
printf("pa_mainloop_run() failed.");
return;
}
else
printf("Pulseaudio threaded mainloop started\n");
pa_threaded_mainloop_lock(pa_ml);
if (RemotePulseDevices[0]) { //we've got at least 1 remote device
pa_IQ_ctx = pa_context_new(pa_mlapi, "Quisk-remote");
if (pa_context_connect(pa_IQ_ctx, quisk_sound_state.IQ_server, 0, NULL) < 0)
printf("Failed to connect to remote Pulseaudio server\n");
pa_context_set_state_callback(pa_IQ_ctx, state_cb, RemotePulseDevices); //send a list of remote devices to open
}
if (LocalPulseDevices[0]) { //we've got at least 1 local device
pa_ctx = pa_context_new(pa_mlapi, "Quisk-local");
if (pa_context_connect(pa_ctx, NULL, 0, NULL) < 0)
printf("Failed to connect to local Pulseaudio server\n");
pa_context_set_state_callback(pa_ctx, state_cb, LocalPulseDevices);
}
pa_threaded_mainloop_unlock(pa_ml);
for(i=0; LocalPulseDevices[i]; i++) {
num_pa_devices++;
}
for(i=0; RemotePulseDevices[i]; i++) {
num_pa_devices++;
}
if (quisk_sound_state.verbose_pulse)
printf("Waiting for %d streams to start\n", num_pa_devices);
while (streams_to_start < num_pa_devices); // wait for all the devices to open
if (quisk_sound_state.verbose_pulse)
printf("All streams started\n");
}
// Close all streams/contexts/loops and return
void quisk_close_sound_pulseaudio() {
int i = 0;
if (quisk_sound_state.verbose_pulse)
printf("Closing Pulseaudio interfaces \n ");
while (OpenPulseDevices[i]) {
pa_stream_disconnect(OpenPulseDevices[i]);
pa_stream_unref(OpenPulseDevices[i]);
OpenPulseDevices[i] = '\0';
i++;
}
if (quisk_sound_state.verbose_pulse)
printf("Waiting for %d streams to disconnect\n", streams_ready);
while(streams_ready > 0);
if (pa_IQ_ctx) {
pa_context_disconnect(pa_IQ_ctx);
pa_context_unref(pa_IQ_ctx);
pa_IQ_ctx = NULL;
}
if (pa_ctx) {
pa_context_disconnect(pa_ctx);
pa_context_unref(pa_ctx);
pa_ctx = NULL;
}
if (pa_ml) {
pa_threaded_mainloop_stop(pa_ml);
pa_threaded_mainloop_free(pa_ml);
pa_ml = NULL;
}
}
// Additional bugs added by N2ADR below this point.
// Code for quisk_pa_sound_devices is based on code by Igor Brezac and Eric Connell, and Jan Newmarch.
// This should only be called when Quisk first starts, but names changed to protect the other mainloop.
// This callback gets called when our context changes state. We really only
// care about when it's ready or if it has failed.
static void pa_names_state_cb(pa_context *c, void *userdata) {
pa_context_state_t ctx_state;
int *main_state = userdata;
ctx_state = pa_context_get_state(c);
switch (ctx_state) {
// There are just here for reference
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
default:
break;
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
*main_state = 9;
break;
case PA_CONTEXT_READY:
*main_state = 1;
break;
}
}
static void source_sink(const char * name, const char * descr, pa_proplist * props, PyObject * pylist) {
const char * value;
char buf300[300];
PyObject * pytup;
pytup = PyTuple_New(3);
PyList_Append(pylist, pytup);
PyTuple_SET_ITEM(pytup, 0, PyString_FromString(name));
PyTuple_SET_ITEM(pytup, 1, PyString_FromString(descr));
value = pa_proplist_gets(props, "device.api");
if (value && ! strcmp(value, "alsa")) {
snprintf(buf300, 300, "%s %s (hw:%s,%s)", pa_proplist_gets(props, "alsa.card_name"), pa_proplist_gets(props, "alsa.name"),
pa_proplist_gets(props, "alsa.card"), pa_proplist_gets(props, "alsa.device"));
PyTuple_SET_ITEM(pytup, 2, PyString_FromString(buf300));
}
else {
PyTuple_SET_ITEM(pytup, 2, PyString_FromString(""));
}
}
// pa_mainloop will call this function when it's ready to tell us about a sink.
static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
if (eol > 0) // If eol is set to a positive number, you're at the end of the list
return;
source_sink(l->name, l->description, l->proplist, (PyObject *)userdata);
if ( ! strcmp(l->name, "QuiskDigitalInput"))
have_QuiskDigitalInput = 1;
if ( ! strcmp(l->name, "QuiskDigitalOutput"))
have_QuiskDigitalOutput = 1;
}
static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) {
if (eol > 0)
return;
source_sink(l->name, l->description, l->proplist, (PyObject *)userdata);
}
static void index_callback(pa_context *c, uint32_t idx, void *userdata) {
//printf("%u\n", idx);
}
PyObject * quisk_pa_sound_devices(PyObject * self, PyObject * args)
{ // Return a list of PulseAudio device names [pycapt, pyplay]
PyObject * pylist, * pycapt, * pyplay;
pa_mainloop *pa_names_ml;
pa_mainloop_api *pa_names_mlapi;
pa_operation *pa_op=NULL;
pa_context *pa_names_ctx;
int state = 0;
if (!PyArg_ParseTuple (args, ""))
return NULL;
// Each pycapt and pyplay is (dev name, description, alsa name)
pylist = PyList_New(0); // list [pycapt, pyplay]
pycapt = PyList_New(0); // list of capture devices
pyplay = PyList_New(0); // list of play devices
PyList_Append(pylist, pycapt);
PyList_Append(pylist, pyplay);
//printf("Starting name loop\n");
// Create a mainloop API and connection to the default server
pa_names_ml = pa_mainloop_new();
pa_names_mlapi = pa_mainloop_get_api(pa_names_ml);
pa_names_ctx = pa_context_new(pa_names_mlapi, "DeviceNames");
// This function connects to the pulse server
if (pa_context_connect(pa_names_ctx, NULL, 0, NULL) < 0) {
if (quisk_sound_state.verbose_pulse)
printf("No local daemon to connect to for show_pulse_audio_devices option\n");
return pylist;
}
// This function defines a callback so the server will tell us it's state.
pa_context_set_state_callback(pa_names_ctx, pa_names_state_cb, &state);
// Now we'll enter into an infinite loop until we get the data we receive or if there's an error
while (state < 10) {
switch (state) {
case 0: // We can't do anything until PA is ready
pa_mainloop_iterate(pa_names_ml, 1, NULL);
break;
case 1:
// This sends an operation to the server. pa_sinklist_info is
// our callback function and a pointer to our devicelist will
// be passed to the callback.
pa_op = pa_context_get_sink_info_list(pa_names_ctx, pa_sinklist_cb, pyplay);
// Update state for next iteration through the loop
state++;
pa_mainloop_iterate(pa_names_ml, 1, NULL);
break;
case 2:
// Now we wait for our operation to complete. When it's
// complete our pa_output_devicelist is filled out, and we move
// along to the next state
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
pa_operation_unref(pa_op);
// Now we perform another operation to get the source
// (input device) list just like before.
pa_op = pa_context_get_source_info_list(pa_names_ctx, pa_sourcelist_cb, pycapt);
// Update the state so we know what to do next
state++;
}
pa_mainloop_iterate(pa_names_ml, 1, NULL);
break;
case 3:
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
pa_operation_unref(pa_op);
state = 4;
}
else
pa_mainloop_iterate(pa_names_ml, 1, NULL);
break;
// The following loads modules for digital input and output if they are not present.
case 4:
if ( ! have_QuiskDigitalInput) {
pa_op = pa_context_load_module(pa_names_ctx, "module-null-sink",
"sink_name=QuiskDigitalInput sink_properties=device.description=QuiskDigitalInput",
index_callback, NULL);
state = 5;
pa_mainloop_iterate(pa_names_ml, 1, NULL);
}
else
state = 6;
break;
case 5:
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
pa_operation_unref(pa_op);
state = 6;
}
else
pa_mainloop_iterate(pa_names_ml, 1, NULL);
break;
case 6:
if ( ! have_QuiskDigitalOutput) {
pa_op = pa_context_load_module(pa_names_ctx, "module-null-sink",
"sink_name=QuiskDigitalOutput sink_properties=device.description=QuiskDigitalOutput",
index_callback, NULL);
state = 7;
pa_mainloop_iterate(pa_names_ml, 1, NULL);
}
else
state = 9;
break;
case 7:
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
pa_operation_unref(pa_op);
state = 9;
}
else
pa_mainloop_iterate(pa_names_ml, 1, NULL);
break;
case 9: // Now we're done, clean up and disconnect and return
pa_context_disconnect(pa_names_ctx);
pa_context_unref(pa_names_ctx);
pa_mainloop_free(pa_names_ml);
state = 99;
break;
}
}
//printf("Finished with name loop\n");
return pylist;
}