1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
|
/*
* Copyright (c) Linux community.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "pulseaudio.h"
#include "support.h" // _(x)
#include "log.h"
#include "utility.h"
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <gdk/gdk.h>
#include <pulse/pulseaudio.h>
// Original version of this code is from:
// http://pulseaudio.org/wiki/SampleAsyncDeviceList
// Local $HOME/.pulse/ directory
#define LOCAL_PULSEAUDIO_DIR ".pulse"
// Audio sink devices (local to this module)
static GList *g_sink_list = NULL;
// Audio source devices (local to this module)
static GList *g_source_list = NULL;
static void pa_clear_lists();
void pa_get_device_lists();
static void pa_get_device_lists_ext();
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);
void pulseaudio_module_init() {
LOG_DEBUG("Init pulseaudio.c.\n");
g_source_list = NULL;
g_sink_list = NULL;
}
void pulseaudio_module_exit() {
LOG_DEBUG("Clean up pulseaudio.c.\n");
// Clear lists
pa_clear_lists();
}
// ---------------------------------------------------
GList *pa_get_source_list() {
// Return g_source_list to the caller
// Load both g_source_devices and g_sink_devices
pa_get_device_lists();
// Return g_source_list
return g_source_list;
}
static void pa_clear_lists() {
LOG_DEBUG("pa_clear_lists()\n");
// Free g_sink_list
audio_sources_free_list(g_sink_list);
g_sink_list = NULL;
// Free g_source_list
audio_sources_free_list(g_source_list);
g_source_list = NULL;
}
void pa_get_device_lists() {
// Clear both g_sink_list and g_source_list
pa_clear_lists();
// Reload both g_sink_list and g_source_list.
pa_get_device_lists_ext();
// Debug print lists
#ifdef ACTIVE_DEBUGGING
audio_sources_print_list(g_sink_list, "g_sink_list, PULSEAUDIO SINK DEVICES");
#endif
// Copy the g_sink_list[i]->description to g_sources[i]->description + add "(Audio Output)" label to the tail.
GList *source = g_list_first(g_source_list);
while (source) {
DeviceItem *source_item = (DeviceItem*)source->data;
if (source_item->type == AUDIO_SINK_MONITOR) {
GList *sink = g_list_nth(g_sink_list, source_item->sink_index);
if (sink) {
DeviceItem *sink_item = (DeviceItem*)sink->data;
// Free the old decsription
g_free(source_item->description);
// Take the sink_item->description and add word "(Audio output)" to the tail.
// Translators: "(Audio output)" is normally a loudspeaker that produces sound.
source_item->description = g_strdup_printf("%s %s", sink_item->description, _("(Audio output)"));
}
}
// Next
source = g_list_next(source);
}
#ifdef ACTIVE_DEBUGGING
audio_sources_print_list(g_source_list, "g_source_list, PULSEAUDIO SOURCE DEVICES");
#endif
}
static void pa_get_device_lists_ext() {
// Define our pulse audio loop and connection variables
pa_mainloop *pa_ml = NULL;
pa_mainloop_api *pa_mlapi = NULL;
pa_operation *pa_op = NULL;
pa_context *pa_ctx = NULL;
// We'll need these state variables to keep track of our requests
int state = 0;
int pa_ready = 0;
// 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, 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);
gint result = 0;
// Now we'll enter into an infinite loop until we get the data we receive
// or if there's an error
while (!result) {
// 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);
result = -1;
break;
}
// 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:
// NULL == we do not collect sink (output) devices.
pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, NULL);
// Update state for next iteration through the loop
state++;
break;
case 1:
// Now we wait for our operation to complete.
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
pa_operation_unref(pa_op);
// Get the source (input devices) list. We pass a pointer to our list_holder.
pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, NULL);
// 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
pa_operation_unref(pa_op);
pa_context_disconnect(pa_ctx);
pa_context_unref(pa_ctx);
pa_mainloop_free(pa_ml);
result = 1;
}
break;
default:
// We should never see this state
fprintf(stderr, "Error state %d\n", state);
result = -1;
}
if (result != 0) break;
// 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 = 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.
static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
// Collect sink devices.
// See: http://0pointer.de/lennart/projects/pulseaudio/doxygen/structpa__sink__info.html
if (!l || eol > 0) return;
// Copy the description and cut it nicely
gchar *descr = g_strdup((gchar*)l->description);
str_cut_nicely(descr, 39/*to len*/, 25/*minimum len*/);
// Create new DeviceItem
DeviceItem *item = device_item_create((gchar*)l->name, descr);
// Free descr
g_free(descr);
// This is a sound sink, normally real audio card with loudspeakers
item->type = AUDIO_SINK;
// Add to g_sinks list
g_sink_list = g_list_append(g_sink_list, item);
// Set icon (audio card)
item->icon_name = g_strdup("audio-card.png");
}
// 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) {
// Collect input devices (audio sources).
// See: http://0pointer.de/lennart/projects/pulseaudio/doxygen/structpa__source__info.html
if (!l || eol > 0) return;
// Copy the description and cut it nicely
gchar *descr = g_strdup((gchar*)l->description);
str_cut_nicely(descr, 39/*to len*/, 25/*minimum len*/);
// Create new DeviceItem
DeviceItem *item = device_item_create((gchar*)l->name, descr);
// Free descr
g_free(descr);
// This maybe a (*.monitor) device that points to a real AUDIO_SINK (sound-card), see above.
// Save the monitor_of_sink index.
item->sink_index = l->monitor_of_sink;
if (item->sink_index != PA_INVALID_INDEX) {
// Monitor device for a real sound-card (we can record from this)
item->type = AUDIO_SINK_MONITOR;
// Set icon (audio card with microphone)
item->icon_name = g_strdup("loudspeaker.png");
} else {
// Device with audio input (microphone)
item->type = AUDIO_INPUT;
// Add "Microphone" to the device description
gchar *descr = g_strdup_printf("%s %s", item->description, _("(Microphone)"));
g_free(item->description);
item->description = descr;
// Find a suitable icon.
// TODO: Make this test better.
if (audio_sources_device_is_webcam(item->description)) {
// Most likely a webcam
item->icon_name = g_strdup("webcam.png");
} else {
// Ordinary microphone
item->icon_name = g_strdup("microphone.png");
}
}
// Add to g_sources list
g_source_list = g_list_append(g_source_list, item);
}
#if 0
These functions are not in use:
gchar *pa_get_saved_device(gchar *file_pattern) {
// See:
// $ ls -l $HOME/.pulse/
//
// Typical default files for sink and source devices are:
// $HOME/.pulse/b3b6877727df4f4ae34dba2b00000009-default-source
// $HOME/.pulse/b3b6877727df4f4ae34dba2b00000009-default-sink
gchar *home = get_home_dir();
gchar *path = g_strdup_printf("%s/%s", home, LOCAL_PULSEAUDIO_DIR);
GList *list = get_directory_listing(path, file_pattern);
g_free(home);
g_free(path);
// Get first filename (should be only one)
gchar *filename = (gchar*)g_list_nth_data(list, 0);
gchar *device_id = NULL;
if (filename) {
GError *error;
device_id = read_file_content(filename, &error);
g_strstrip(device_id);
if (error) {
g_error_free(error);
}
}
g_list_foreach(list, (GFunc)g_free, NULL);
g_list_free(list);
list = NULL;
return device_id;
}
gchar *pa_get_default_sink() {
// TODO:
// Try to find a GStreamer or Pulseaudio function that returns the default sink device.
// I would like to use
// pa_sink *sink = pa_namereg_get_default_sink(pa_core *c);
// But documentation for it is not easy to find.
// Get the default sink device from $HOME/.pulse/*-default-sink file.
// You can see this setting in the "gnome-volume-control". Look for [Output] page, and value for "Choose a device for sound output".
gchar *device_id = pa_get_saved_device("*-default-sink");
// The caller should g_free() this value
return device_id;
}
gchar *pa_get_default_source() {
// TODO:
// Try to find a GStreamer or Pulseaudio function that returns the default source device.
// I would like to use
// pa_source* pa_namereg_get_default_source(pa_core *c)
// But documentation for it is not easy to find.
// Get the default source device from $HOME/.pulse/*-default-source file.
// You can see this setting in the "gnome-volume-control". Look for [Input] page, and value for "Choose a device for sound input".
gchar *device_id = pa_get_saved_device("*-default-source");
// The caller should g_free() this value
return device_id;
}
#endif
|