qtsoundmodem/pulse.c

519 lines
15 KiB
C
Raw Normal View History

2023-09-04 19:06:44 +01:00
// Pulse Audio bits for QtSoundmodem
#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#define UNUSED(x) (void)(x)
extern char CaptureNames[16][256];
extern char PlaybackNames[16][256];
extern int PlaybackCount;
extern int CaptureCount;
#include <dlfcn.h>
void *handle = NULL;
void *shandle = NULL;
pa_mainloop * (*ppa_mainloop_new)(void);
pa_mainloop_api * (*ppa_mainloop_get_api)(pa_mainloop * m);
pa_context * (*ppa_context_new)(pa_mainloop_api *mainloop, const char *name);
int (*ppa_context_connect)(pa_context * c, const char * server, pa_context_flags_t flags, const pa_spawn_api * api);
void (*ppa_context_set_state_callback)(pa_context * c, pa_context_notify_cb_t cb, void * userdata);
int (*ppa_mainloop_iterate)(pa_mainloop * m, int block, int * retval);
void (*ppa_mainloop_free)(pa_mainloop * m);
void (*ppa_context_disconnect)(pa_context * c);
void (*ppa_context_unref)(pa_context * c);
const char * (*ppa_strerror)(int error);
pa_context_state_t(*ppa_context_get_state)(const pa_context *c);
pa_operation * (*ppa_context_get_sink_info_list)(pa_context * c, pa_sink_info_cb_t cb, void * userdata);
pa_operation * (*ppa_context_get_source_info_list)(pa_context * c, pa_source_info_cb_t cb, void * userdata);
void (*ppa_operation_unref)(pa_operation * o);
pa_operation_state_t(*ppa_operation_get_state)(const pa_operation * o);
pa_simple * (*ppa_simple_new)(const char * server,
const char * name,
pa_stream_direction_t dir,
const char * dev,
const char * stream_name,
const pa_sample_spec * ss,
const pa_channel_map * map,
const pa_buffer_attr * attr,
int * error) = NULL;
pa_usec_t(*ppa_simple_get_latency)(pa_simple * s, int * error);
int(*ppa_simple_read)(pa_simple * s, void * data, size_t bytes, int * error);
int(*ppa_simple_write)(pa_simple * s, void * data, size_t bytes, int * error);
int(*ppa_simple_flush)(pa_simple * s, int * error);
void(*ppa_simple_free)(pa_simple * s);
int(*ppa_simple_drain)(pa_simple * s, int * error);
void * getModule(void *handle, char * sym)
{
return dlsym(handle, sym);
}
void * initPulse()
{
// Load the pulse libraries
if (handle)
return handle; // already done
handle = dlopen("libpulse.so", RTLD_LAZY);
if (!handle)
{
fputs(dlerror(), stderr);
return NULL;
}
if ((ppa_mainloop_new = getModule(handle, "pa_mainloop_new")) == NULL) return NULL;
if ((ppa_mainloop_get_api = getModule(handle, "pa_mainloop_get_api")) == NULL) return NULL;
if ((ppa_context_new = getModule(handle, "pa_context_new")) == NULL) return NULL;
if ((ppa_context_connect = getModule(handle, "pa_context_connect")) == NULL) return NULL;
if ((ppa_context_set_state_callback = getModule(handle, "pa_context_set_state_callback")) == NULL) return NULL;
if ((ppa_mainloop_iterate = getModule(handle, "pa_mainloop_iterate")) == NULL) return NULL;
if ((ppa_mainloop_free = getModule(handle, "pa_mainloop_free")) == NULL) return NULL;
if ((ppa_context_disconnect = getModule(handle, "pa_context_disconnect")) == NULL) return NULL;
if ((ppa_context_unref = getModule(handle, "pa_context_unref")) == NULL) return NULL;
if ((ppa_strerror = getModule(handle, "pa_strerror")) == NULL) return NULL;
if ((ppa_context_get_state = getModule(handle, "pa_context_get_state")) == NULL) return NULL;
if ((ppa_context_get_sink_info_list = getModule(handle, "pa_context_get_sink_info_list")) == NULL) return NULL;
if ((ppa_context_get_source_info_list = getModule(handle, "pa_context_get_source_info_list")) == NULL) return NULL;
if ((ppa_operation_unref = getModule(handle, "pa_operation_unref")) == NULL) return NULL;
if ((ppa_operation_get_state = getModule(handle, "pa_operation_get_state")) == NULL) return NULL;
shandle = dlopen("libpulse-simple.so", RTLD_LAZY);
if (!shandle)
{
fputs(dlerror(), stderr);
return NULL;
}
if ((ppa_simple_new = getModule(shandle, "pa_simple_new")) == NULL) return NULL;
if ((ppa_simple_get_latency = getModule(shandle, "pa_simple_get_latency")) == NULL) return NULL;
if ((ppa_simple_read = dlsym(shandle, "pa_simple_read")) == NULL) return NULL;
if ((ppa_simple_write = dlsym(shandle, "pa_simple_write")) == NULL) return NULL;
if ((ppa_simple_flush = dlsym(shandle, "pa_simple_flush")) == NULL) return NULL;
if ((ppa_simple_drain = dlsym(shandle, "pa_simple_drain")) == NULL) return NULL;
if ((ppa_simple_free = dlsym(shandle, "pa_simple_free")) == NULL) return NULL;
return shandle;
}
// Field list is here: http://0pointer.de/lennart/projects/pulseaudio/doxygen/structpa__sink__info.html
typedef struct pa_devicelist {
uint8_t initialized;
char name[512];
uint32_t index;
char description[256];
} pa_devicelist_t;
void pa_state_cb(pa_context *c, void *userdata);
void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata);
void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata);
int pa_get_devicelist(pa_devicelist_t *input, pa_devicelist_t *output);
// This callback gets called when our context changes state. We really only
// care about when it's ready or if it has failed
void pa_state_cb(pa_context *c, void *userdata) {
pa_context_state_t state;
int *pa_ready = userdata;
state = ppa_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:
*pa_ready = 2;
break;
case PA_CONTEXT_READY:
*pa_ready = 1;
break;
}
}
// pa_mainloop will call this function when it's ready to tell us about a sink.
// Since we're not threading, there's no need for mutexes on the devicelist
// structure
void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata)
{
UNUSED(c);
pa_devicelist_t *pa_devicelist = userdata;
int ctr = 0;
// If eol is set to a positive number, you're at the end of the list
if (eol > 0) {
return;
}
// We know we've allocated 16 slots to hold devices. Loop through our
// structure and find the first one that's "uninitialized." Copy the
// contents into it and we're done. If we receive more than 16 devices,
// they're going to get dropped. You could make this dynamically allocate
// space for the device list, but this is a simple example.
for (ctr = 0; ctr < 16; ctr++) {
if (!pa_devicelist[ctr].initialized) {
strncpy(pa_devicelist[ctr].name, l->name, 511);
strncpy(pa_devicelist[ctr].description, l->description, 255);
pa_devicelist[ctr].index = l->index;
pa_devicelist[ctr].initialized = 1;
break;
}
}
}
// See above. This callback is pretty much identical to the previous
void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata)
{
UNUSED(c);
pa_devicelist_t *pa_devicelist = userdata;
int ctr = 0;
if (eol > 0) {
return;
}
for (ctr = 0; ctr < 16; ctr++) {
if (!pa_devicelist[ctr].initialized) {
strncpy(pa_devicelist[ctr].name, l->name, 511);
strncpy(pa_devicelist[ctr].description, l->description, 255);
pa_devicelist[ctr].index = l->index;
pa_devicelist[ctr].initialized = 1;
break;
}
}
}
int pa_get_devicelist(pa_devicelist_t *input, pa_devicelist_t *output) {
// Define our pulse audio loop and connection variables
pa_mainloop *pa_ml;
pa_mainloop_api *pa_mlapi;
pa_operation *pa_op;
pa_context *pa_ctx;
// We'll need these state variables to keep track of our requests
int state = 0;
int pa_ready = 0;
// Initialize our device lists
memset(input, 0, sizeof(pa_devicelist_t) * 16);
memset(output, 0, sizeof(pa_devicelist_t) * 16);
// Create a mainloop API and connection to the default server
pa_ml = ppa_mainloop_new();
pa_mlapi = ppa_mainloop_get_api(pa_ml);
pa_ctx = ppa_context_new(pa_mlapi, "test");
// This function connects to the pulse server
ppa_context_connect(pa_ctx, NULL, 0, NULL);
// This function defines a callback so the server will tell us it's state.
// Our callback will wait for the state to be ready. The callback will
// modify the variable to 1 so we know when we have a connection and it's
// ready.
// If there's an error, the callback will set pa_ready to 2
ppa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready);
// Now we'll enter into an infinite loop until we get the data we receive
// or if there's an error
for (;;) {
// We can't do anything until PA is ready, so just iterate the mainloop
// and continue
if (pa_ready == 0) {
ppa_mainloop_iterate(pa_ml, 1, NULL);
continue;
}
// We couldn't get a connection to the server, so exit out
if (pa_ready == 2) {
ppa_context_disconnect(pa_ctx);
ppa_context_unref(pa_ctx);
ppa_mainloop_free(pa_ml);
return -1;
}
// At this point, we're connected to the server and ready to make
// requests
switch (state) {
// State 0: we haven't done anything yet
case 0:
// 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 The operation ID is stored in the
// pa_op variable
pa_op = ppa_context_get_sink_info_list(pa_ctx,
pa_sinklist_cb,
output
);
// Update state for next iteration through the loop
state++;
break;
case 1:
// 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 (ppa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
ppa_operation_unref(pa_op);
// Now we perform another operation to get the source
// (input device) list just like before. This time we pass
// a pointer to our input structure
pa_op = ppa_context_get_source_info_list(pa_ctx,
pa_sourcelist_cb,
input
);
// Update the state so we know what to do next
state++;
}
break;
case 2:
if (ppa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
// Now we're done, clean up and disconnect and return
ppa_operation_unref(pa_op);
ppa_context_disconnect(pa_ctx);
ppa_context_unref(pa_ctx);
ppa_mainloop_free(pa_ml);
return 0;
}
break;
default:
// We should never see this state
fprintf(stderr, "in state %d\n", state);
return -1;
}
// Iterate the main loop and go again. The second argument is whether
// or not the iteration should block until something is ready to be
// done. Set it to zero for non-blocking.
ppa_mainloop_iterate(pa_ml, 1, NULL);
}
}
int listpulse()
{
int ctr;
PlaybackCount = 0;
CaptureCount = 0;
// This is where we'll store the input device list
pa_devicelist_t pa_input_devicelist[16];
// This is where we'll store the output device list
pa_devicelist_t pa_output_devicelist[16];
if (pa_get_devicelist(pa_input_devicelist, pa_output_devicelist) < 0) {
fprintf(stderr, "failed to get device list\n");
return 1;
}
printf("Pulse Playback Devices\n\n");
for (ctr = 0; ctr < 16; ctr++)
{
if (!pa_output_devicelist[ctr].initialized)
break;
printf("Name: %s\n", pa_output_devicelist[ctr].name);
strcpy(&PlaybackNames[PlaybackCount++][0], pa_output_devicelist[ctr].name);
}
printf("Pulse Capture Devices\n\n");
for (ctr = 0; ctr < 16; ctr++)
{
if (!pa_input_devicelist[ctr].initialized)
break;
printf("Name: %s\n", pa_input_devicelist[ctr].name);
strcpy(&CaptureNames[CaptureCount++][0], pa_input_devicelist[ctr].name);
}
return 0;
}
pa_simple * OpenPulsePlayback(char * Server)
{
pa_simple * s;
pa_sample_spec ss;
ss.format = PA_SAMPLE_S16NE;
ss.channels = 2;
ss.rate = 12000;
int error;
s = (*ppa_simple_new)(NULL, // Use the default server.
"QtSM", // Our application's name.
PA_STREAM_PLAYBACK,
Server,
"Playback", // Description of our stream.
&ss, // Our sample format.
NULL, // Use default channel map
NULL, // Use default buffering attributes.
&error
);
if (s == 0)
printf("Playback pa_simple_new() failed: %s\n", ppa_strerror(error));
else
printf("Playback Handle %x\n", (unsigned int)s);
return s;
}
pa_simple * OpenPulseCapture(char * Server)
{
pa_simple * s;
pa_sample_spec ss;
ss.format = PA_SAMPLE_S16NE;
ss.channels = 2;
ss.rate = 12000;
int error;
pa_buffer_attr attr;
attr.maxlength = -1;
attr.tlength = -1;
attr.prebuf = -1;
attr.minreq = -1;
attr.fragsize = 512;
s = (*ppa_simple_new)(NULL, // Use the default server.
"QtSM", // Our application's name.
PA_STREAM_RECORD,
Server,
"Capture", // Description of our stream.
&ss, // Our sample format.
NULL, // Use default channel map
&attr,
&error
);
if (s == 0)
printf("Capture pa_simple_new() failed: %s\n", ppa_strerror(error));
else
printf("Capture Handle %x\n", (unsigned int)s);
return s;
}
pa_simple * spc = 0; // Capure Handle
pa_simple * spp = 0; // Playback Handle
int pulse_audio_open(char * CaptureDevice, char * PlaybackDevice)
{
pa_usec_t latency;
int error;
spc = OpenPulseCapture(CaptureDevice);
spp = OpenPulsePlayback(PlaybackDevice);
if (spc && spp)
{
if ((latency = ppa_simple_get_latency(spc, &error)) == (pa_usec_t)-1) {
printf("cap simple_get_latency() failed: %s\n", ppa_strerror(error));
}
else
printf("cap %0.0f usec \n", (float)latency);
if ((latency = ppa_simple_get_latency(spp, &error)) == (pa_usec_t)-1) {
printf("play simple_get_latency() failed: %s\n", ppa_strerror(error));
}
else
printf("play %0.0f usec \n", (float)latency);
return 1;
}
else
return 0;
}
void pulse_audio_close()
{
int error;
ppa_simple_flush(spc, &error);
ppa_simple_free(spc);
spc = 0;
ppa_simple_drain(spp, &error);
ppa_simple_free(spp);
spp = 0;
}
int pulse_read(short * samples, int nSamples)
{
int error;
int nBytes = nSamples * 4;
if (spc == 0)
return 0;
if (ppa_simple_read(spc, samples, nBytes, &error) < 0)
{
printf("Pulse pa_simple_read() failed: %s\n", ppa_strerror(error));
return 0;
}
return nSamples;
}
int pulse_write(short * ptr, int len)
{
int k;
int error;
if (spp == 0)
return 0;
k = ppa_simple_write(spp, ptr, len * 4, &error);
if (k < 0)
{
printf("Pulse pa_simple_write() failed: %s\n", ppa_strerror(error));
return -1;
}
return 0;
}
void pulse_flush()
{
int error;
if (spp == 0)
return;
if (ppa_simple_flush(spp, &error) < 0)
printf("Pulse pa_simple_flush() failed: %s\n", ppa_strerror(error));
}