aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/media_plugins/webkit/linux_volume_catcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'linden/indra/media_plugins/webkit/linux_volume_catcher.cpp')
-rw-r--r--linden/indra/media_plugins/webkit/linux_volume_catcher.cpp477
1 files changed, 477 insertions, 0 deletions
diff --git a/linden/indra/media_plugins/webkit/linux_volume_catcher.cpp b/linden/indra/media_plugins/webkit/linux_volume_catcher.cpp
new file mode 100644
index 0000000..c4c4181
--- /dev/null
+++ b/linden/indra/media_plugins/webkit/linux_volume_catcher.cpp
@@ -0,0 +1,477 @@
1/**
2 * @file linux_volume_catcher.cpp
3 * @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources
4 *
5 * @cond
6 * $LicenseInfo:firstyear=2010&license=viewergpl$
7 *
8 * Copyright (c) 2010, Linden Research, Inc.
9 *
10 * Second Life Viewer Source Code
11 * The source code in this file ("Source Code") is provided by Linden Lab
12 * to you under the terms of the GNU General Public License, version 2.0
13 * ("GPL"), unless you have obtained a separate licensing agreement
14 * ("Other License"), formally executed by you and Linden Lab. Terms of
15 * the GPL can be found in doc/GPL-license.txt in this distribution, or
16 * online at http://secondlife.com/developers/opensource/gplv2
17 *
18 * There are special exceptions to the terms and conditions of the GPL as
19 * it is applied to this Source Code. View the full text of the exception
20 * in the file doc/FLOSS-exception.txt in this software distribution, or
21 * online at
22 * http://secondlife.com/developers/opensource/flossexception
23 *
24 * By copying, modifying or distributing this software, you acknowledge
25 * that you have read and understood your obligations described above,
26 * and agree to abide by those obligations.
27 *
28 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
29 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
30 * COMPLETENESS OR PERFORMANCE.
31 * $/LicenseInfo$
32 *
33 * @endcond
34 */
35
36/*
37 The high-level design is as follows:
38 1) Connect to the PulseAudio daemon
39 2) Watch for the creation of new audio players connecting to the daemon (this includes ALSA clients running on the PulseAudio emulation layer, such as Flash plugins)
40 3) Examine any new audio player's PID to see if it belongs to our own process
41 4) If so, tell PA to adjust the volume of that audio player ('sink input' in PA parlance)
42 5) Keep a list of all living audio players that we care about, adjust the volumes of all of them when we get a new setVolume() call
43 */
44
45# include <set> //imprudence
46
47#include "linden_common.h"
48
49#include "volume_catcher.h"
50
51
52extern "C" {
53#include <glib.h>
54#include <glib-object.h>
55
56#include <pulse/introspect.h>
57#include <pulse/context.h>
58#include <pulse/subscribe.h>
59#include <pulse/glib-mainloop.h> // There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken.
60
61#include "apr_pools.h"
62#include "apr_dso.h"
63}
64
65////////////////////////////////////////////////////
66
67#define DEBUGMSG(...) do {} while(0)
68#define INFOMSG(...) do {} while(0)
69#define WARNMSG(...) do {} while(0)
70
71#define LL_PA_SYM(REQUIRED, PASYM, RTN, ...) RTN (*ll##PASYM)(__VA_ARGS__) = NULL
72#include "linux_volume_catcher_pa_syms.inc"
73#include "linux_volume_catcher_paglib_syms.inc"
74#undef LL_PA_SYM
75
76static bool sSymsGrabbed = false;
77static apr_pool_t *sSymPADSOMemoryPool = NULL;
78static apr_dso_handle_t *sSymPADSOHandleG = NULL;
79
80bool grab_pa_syms(std::string pulse_dso_name)
81{
82 if (sSymsGrabbed)
83 {
84 // already have grabbed good syms
85 return true;
86 }
87
88 bool sym_error = false;
89 bool rtn = false;
90 apr_status_t rv;
91 apr_dso_handle_t *sSymPADSOHandle = NULL;
92
93#define LL_PA_SYM(REQUIRED, PASYM, RTN, ...) do{rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll##PASYM, sSymPADSOHandle, #PASYM); if (rv != APR_SUCCESS) {INFOMSG("Failed to grab symbol: %s", #PASYM); if (REQUIRED) sym_error = true;} else DEBUGMSG("grabbed symbol: %s from %p", #PASYM, (void*)ll##PASYM);}while(0)
94
95 //attempt to load the shared library
96 apr_pool_create(&sSymPADSOMemoryPool, NULL);
97
98 if ( APR_SUCCESS == (rv = apr_dso_load(&sSymPADSOHandle,
99 pulse_dso_name.c_str(),
100 sSymPADSOMemoryPool) ))
101 {
102 INFOMSG("Found DSO: %s", pulse_dso_name.c_str());
103
104#include "linux_volume_catcher_pa_syms.inc"
105#include "linux_volume_catcher_paglib_syms.inc"
106
107 if ( sSymPADSOHandle )
108 {
109 sSymPADSOHandleG = sSymPADSOHandle;
110 sSymPADSOHandle = NULL;
111 }
112
113 rtn = !sym_error;
114 }
115 else
116 {
117 INFOMSG("Couldn't load DSO: %s", pulse_dso_name.c_str());
118 rtn = false; // failure
119 }
120
121 if (sym_error)
122 {
123 WARNMSG("Failed to find necessary symbols in PulseAudio libraries.");
124 }
125#undef LL_PA_SYM
126
127 sSymsGrabbed = rtn;
128 return rtn;
129}
130
131
132void ungrab_pa_syms()
133{
134 // should be safe to call regardless of whether we've
135 // actually grabbed syms.
136
137 if ( sSymPADSOHandleG )
138 {
139 apr_dso_unload(sSymPADSOHandleG);
140 sSymPADSOHandleG = NULL;
141 }
142
143 if ( sSymPADSOMemoryPool )
144 {
145 apr_pool_destroy(sSymPADSOMemoryPool);
146 sSymPADSOMemoryPool = NULL;
147 }
148
149 // NULL-out all of the symbols we'd grabbed
150#define LL_PA_SYM(REQUIRED, PASYM, RTN, ...) do{ll##PASYM = NULL;}while(0)
151#include "linux_volume_catcher_pa_syms.inc"
152#include "linux_volume_catcher_paglib_syms.inc"
153#undef LL_PA_SYM
154
155 sSymsGrabbed = false;
156}
157////////////////////////////////////////////////////
158
159// PulseAudio requires a chain of callbacks with C linkage
160extern "C" {
161 void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata);
162 void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata);
163 void callback_context_state(pa_context *context, void *userdata);
164}
165
166
167class VolumeCatcherImpl
168{
169public:
170 VolumeCatcherImpl();
171 ~VolumeCatcherImpl();
172
173 void setVolume(F32 volume);
174 void pump(void);
175
176 // for internal use - can't be private because used from our C callbacks
177
178 bool loadsyms(std::string pulse_dso_name);
179 void init();
180 void cleanup();
181
182 void update_all_volumes(F32 volume);
183 void update_index_volume(U32 index, F32 volume);
184 void connected_okay();
185
186 std::set<U32> mSinkInputIndices;
187 std::map<U32,U32> mSinkInputNumChannels;
188 F32 mDesiredVolume;
189 pa_glib_mainloop *mMainloop;
190 pa_context *mPAContext;
191 bool mConnected;
192 bool mGotSyms;
193};
194
195VolumeCatcherImpl::VolumeCatcherImpl()
196 : mDesiredVolume(0.0f),
197 mMainloop(NULL),
198 mPAContext(NULL),
199 mConnected(false),
200 mGotSyms(false)
201{
202 init();
203}
204
205VolumeCatcherImpl::~VolumeCatcherImpl()
206{
207 cleanup();
208}
209
210bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name)
211{
212 return grab_pa_syms(pulse_dso_name);
213}
214
215void VolumeCatcherImpl::init()
216{
217 // try to be as defensive as possible because PA's interface is a
218 // bit fragile and (for our purposes) we'd rather simply not function
219 // than crash
220
221 // we cheat and rely upon libpulse-mainloop-glib.so.0 to pull-in
222 // libpulse.so.0 - this isn't a great assumption, and the two DSOs should
223 // probably be loaded separately. Our Linux DSO framework needs refactoring,
224 // we do this sort of thing a lot with practically identical logic...
225 mGotSyms = loadsyms("libpulse-mainloop-glib.so.0");
226 if (!mGotSyms) return;
227
228 // better make double-sure glib itself is initialized properly.
229 if (!g_thread_supported ()) g_thread_init (NULL);
230 g_type_init();
231
232 mMainloop = llpa_glib_mainloop_new(g_main_context_default());
233 if (mMainloop)
234 {
235 pa_mainloop_api *api = llpa_glib_mainloop_get_api(mMainloop);
236 if (api)
237 {
238 pa_proplist *proplist = llpa_proplist_new();
239 if (proplist)
240 {
241 llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player");
242 llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust");
243 llpa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster");
244 llpa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1");
245
246 // plain old pa_context_new() is broken!
247 mPAContext = llpa_context_new_with_proplist(api, NULL, proplist);
248 llpa_proplist_free(proplist);
249 }
250 }
251 }
252
253 // Now we've set up a PA context and mainloop, try connecting the
254 // PA context to a PA daemon.
255 if (mPAContext)
256 {
257 llpa_context_set_state_callback(mPAContext, callback_context_state, this);
258 pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN?
259 if (llpa_context_connect(mPAContext, NULL, cflags, NULL) >= 0)
260 {
261 // Okay! We haven't definitely connected, but we
262 // haven't definitely failed yet.
263 }
264 else
265 {
266 // Failed to connect to PA manager... we'll leave
267 // things like that. Perhaps we should try again later.
268 }
269 }
270}
271
272void VolumeCatcherImpl::cleanup()
273{
274 mConnected = false;
275
276 if (mGotSyms && mPAContext)
277 {
278 llpa_context_disconnect(mPAContext);
279 llpa_context_unref(mPAContext);
280 }
281 mPAContext = NULL;
282
283 if (mGotSyms && mMainloop)
284 {
285 llpa_glib_mainloop_free(mMainloop);
286 }
287 mMainloop = NULL;
288}
289
290void VolumeCatcherImpl::setVolume(F32 volume)
291{
292 mDesiredVolume = volume;
293
294 if (!mGotSyms) return;
295
296 if (mConnected && mPAContext)
297 {
298 update_all_volumes(mDesiredVolume);
299 }
300
301 pump();
302}
303
304void VolumeCatcherImpl::pump()
305{
306 gboolean may_block = FALSE;
307 g_main_context_iteration(g_main_context_default(), may_block);
308}
309
310void VolumeCatcherImpl::connected_okay()
311{
312 pa_operation *op;
313
314 // fetch global list of existing sinkinputs
315 if ((op = llpa_context_get_sink_input_info_list(mPAContext,
316 callback_discovered_sinkinput,
317 this)))
318 {
319 llpa_operation_unref(op);
320 }
321
322 // subscribe to future global sinkinput changes
323 llpa_context_set_subscribe_callback(mPAContext,
324 callback_subscription_alert,
325 this);
326 if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t)
327 (PA_SUBSCRIPTION_MASK_SINK_INPUT),
328 NULL, NULL)))
329 {
330 llpa_operation_unref(op);
331 }
332}
333
334void VolumeCatcherImpl::update_all_volumes(F32 volume)
335{
336 for (std::set<U32>::iterator it = mSinkInputIndices.begin();
337 it != mSinkInputIndices.end(); ++it)
338 {
339 update_index_volume(*it, volume);
340 }
341}
342
343void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume)
344{
345 static pa_cvolume cvol;
346 llpa_cvolume_set(&cvol, mSinkInputNumChannels[index],
347 llpa_sw_volume_from_linear(volume));
348
349 pa_context *c = mPAContext;
350 uint32_t idx = index;
351 const pa_cvolume *cvolumep = &cvol;
352 pa_context_success_cb_t cb = NULL; // okay as null
353 void *userdata = NULL; // okay as null
354
355 pa_operation *op;
356 if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata)))
357 {
358 llpa_operation_unref(op);
359 }
360}
361
362
363void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata)
364{
365 VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
366 llassert(impl);
367
368 if (0 == eol)
369 {
370 pa_proplist *proplist = sii->proplist;
371 pid_t sinkpid = atoll(llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID));
372
373 if (sinkpid == getpid()) // does the discovered sinkinput belong to this process?
374 {
375 bool is_new = (impl->mSinkInputIndices.find(sii->index) ==
376 impl->mSinkInputIndices.end());
377
378 impl->mSinkInputIndices.insert(sii->index);
379 impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels;
380
381 if (is_new)
382 {
383 // new!
384 impl->update_index_volume(sii->index, impl->mDesiredVolume);
385 }
386 else
387 {
388 // seen it already, do nothing.
389 }
390 }
391 }
392}
393
394void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata)
395{
396 VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
397 llassert(impl);
398
399 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
400 case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
401 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
402 PA_SUBSCRIPTION_EVENT_REMOVE)
403 {
404 // forget this sinkinput, if we were caring about it
405 impl->mSinkInputIndices.erase(index);
406 impl->mSinkInputNumChannels.erase(index);
407 }
408 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
409 PA_SUBSCRIPTION_EVENT_NEW)
410 {
411 // ask for more info about this new sinkinput
412 pa_operation *op;
413 if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl)))
414 {
415 llpa_operation_unref(op);
416 }
417 }
418 else
419 {
420 // property change on this sinkinput - we don't care.
421 }
422 break;
423
424 default:;
425 }
426}
427
428void callback_context_state(pa_context *context, void *userdata)
429{
430 VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
431 llassert(impl);
432
433 switch (llpa_context_get_state(context))
434 {
435 case PA_CONTEXT_READY:
436 impl->mConnected = true;
437 impl->connected_okay();
438 break;
439 case PA_CONTEXT_TERMINATED:
440 impl->mConnected = false;
441 break;
442 case PA_CONTEXT_FAILED:
443 impl->mConnected = false;
444 break;
445 default:;
446 }
447}
448
449/////////////////////////////////////////////////////
450
451VolumeCatcher::VolumeCatcher()
452{
453 pimpl = new VolumeCatcherImpl();
454}
455
456VolumeCatcher::~VolumeCatcher()
457{
458 delete pimpl;
459 pimpl = NULL;
460}
461
462void VolumeCatcher::setVolume(F32 volume)
463{
464 llassert(pimpl);
465 pimpl->setVolume(volume);
466}
467
468void VolumeCatcher::setPan(F32 pan)
469{
470 // TODO: implement this (if possible)
471}
472
473void VolumeCatcher::pump()
474{
475 llassert(pimpl);
476 pimpl->pump();
477}