/* * 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