/*
* vim: softtabstop=4 shiftwidth=4 cindent foldmethod=marker expandtab
*
* $LastChangedDate$
* $Revision$
* $LastChangedBy$
* $URL$
*
* Copyright 2009-2011 Eric Connell
* Copyright 2010-2011 Roman Tetelman
*
* This file is part of Mangler.
*
* Mangler 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.
*
* Mangler 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 Mangler. If not, see .
*/
#include "mangler.h"
#ifdef HAVE_PULSE
#include "mangleraudio.h"
#include "manglerpulse.h"
ManglerPulse::ManglerPulse(uint32_t rate, uint8_t channels, uint32_t pcm_framesize) {/*{{{*/
pulse_stream = NULL;
pulse_samplespec.format = PA_SAMPLE_S16NE;
pulse_samplespec.rate = rate;
pulse_samplespec.channels = channels;
buffer_attr.maxlength = -1;
buffer_attr.tlength = -1;
buffer_attr.prebuf = -1;
buffer_attr.minreq = -1;
buffer_attr.fragsize = pcm_framesize;
}/*}}}*/
ManglerPulse::~ManglerPulse() {/*{{{*/
close();
}/*}}}*/
bool
ManglerPulse::open(int type, Glib::ustring device, int rate, int channels) {/*{{{*/
pulse_samplespec.rate = rate;
pulse_samplespec.channels = channels;
if (!(pulse_stream = pa_simple_new(
NULL,
"Mangler",
(type >= AUDIO_OUTPUT) ? PA_STREAM_PLAYBACK : PA_STREAM_RECORD,
(device == "") ? NULL : device.c_str(),
(type >= AUDIO_OUTPUT) ? "Playback" : "Recording",
&pulse_samplespec,
NULL,
(type >= AUDIO_OUTPUT) ? NULL : &buffer_attr,
&pulse_error))) {
fprintf(stderr, "pulse: pa_simple_new() failed: %s\n", pa_strerror(pulse_error));
pulse_stream = NULL;
return false;
}
return true;
}/*}}}*/
void
ManglerPulse::close(bool drain) {/*{{{*/
if (pulse_stream) {
if (drain && pa_simple_drain(pulse_stream, &pulse_error) < 0) {
fprintf(stderr, "pulse: pa_simple_drain() failed: %s\n", pa_strerror(pulse_error));
}
pa_simple_free(pulse_stream);
pulse_stream = NULL;
}
}/*}}}*/
bool
ManglerPulse::write(uint8_t *sample, uint32_t length, int channels) {/*{{{*/
if (!pulse_stream) {
return false;
}
if (pa_simple_write(pulse_stream, sample, length, &pulse_error) < 0) {
fprintf(stderr, "pulse: pa_simple_write() failed: %s\n", pa_strerror(pulse_error));
return false;
}
return true;
}/*}}}*/
bool
ManglerPulse::read(uint8_t *buf) {/*{{{*/
if (!pulse_stream) {
return false;
}
if (pa_simple_read(pulse_stream, buf, buffer_attr.fragsize, &pulse_error) < 0) {
fprintf(stderr, "pulse: pa_simple_read() failed: %s\n", pa_strerror(pulse_error));
return false;
}
return true;
}/*}}}*/
Glib::ustring
ManglerPulse::getAudioSubsystem(void) {/*{{{*/
return Glib::ustring("pulse");
}/*}}}*/
// this is easier in C
typedef struct pa_devicelist {
uint8_t initialized;
char name[512];
uint32_t index;
char description[256];
} pa_devicelist_t;
static void pa_state_cb(pa_context *c, void *userdata);
static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata);
static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata);
static int pa_get_devicelist(pa_devicelist_t *input, pa_devicelist_t *output);
static 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 = pa_mainloop_new();
pa_mlapi = pa_mainloop_get_api(pa_ml);
pa_ctx = pa_context_new(pa_mlapi, "test");
// This function connects to the pulse server
pa_context_connect(pa_ctx, NULL, (pa_context_flags_t)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
pa_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) {
pa_mainloop_iterate(pa_ml, 1, NULL);
continue;
}
// We couldn't get a connection to the server, so exit out
if (pa_ready == 2) {
pa_context_disconnect(pa_ctx);
pa_context_unref(pa_ctx);
pa_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 = pa_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 (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. This time we pass
// a pointer to our input structure
pa_op = pa_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 (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
// Now we're done, clean up and disconnect and return
pa_operation_unref(pa_op);
pa_context_disconnect(pa_ctx);
pa_context_unref(pa_ctx);
pa_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.
pa_mainloop_iterate(pa_ml, 1, NULL);
}
}
// 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_state_cb(pa_context *c, void *userdata) {
pa_context_state_t state;
int *pa_ready = (int *)userdata;
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:
*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
static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
pa_devicelist_t *pa_devicelist = (pa_devicelist_t *)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
static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) {
pa_devicelist_t *pa_devicelist = (pa_devicelist_t *)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;
}
}
}/*}}}*/
void
ManglerPulse::getDeviceList(std::vector& inputDevices, std::vector& outputDevices) {
int ctr;
// 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, "pulse: failed to get device list; is pulseaudio running?\n");
return;
}
for (ctr = 0; ctr < 16; ctr++) {
if (! pa_output_devicelist[ctr].initialized) {
break;
}
outputDevices.push_back(
new ManglerAudioDevice(
pa_output_devicelist[ctr].index,
pa_output_devicelist[ctr].name,
pa_output_devicelist[ctr].description)
);
}
for (ctr = 0; ctr < 16; ctr++) {
if (! pa_input_devicelist[ctr].initialized) {
break;
}
inputDevices.push_back(
new ManglerAudioDevice(
pa_input_devicelist[ctr].index,
pa_input_devicelist[ctr].name,
pa_input_devicelist[ctr].description)
);
}
return;
}
#endif