/** * @file linux_volume_catcher.cpp * @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources * * @cond * $LicenseInfo:firstyear=2010&license=viewergpl$ * * Copyright (c) 2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ * @endcond */ #include "linden_common.h" #include #include #include #include #include // There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken. #include "linux_volume_catcher.h" //////////////////////////////////////////////////// LinuxVolumeCatcher::LinuxVolumeCatcher() { pimpl = new LinuxVolumeCatcherImpl(); } LinuxVolumeCatcher::LinuxVolumeCatcher~() { delete pimpl; pimpl = NULL; } void LinuxVolumeCatcher::setVol(F32 volume) { llassert(pimpl); pimpl->setVol(volume); } void LinuxVolumeCatcher::pump() { llassert(pimpl); pimpl->pump(); } ///////////////////////////////////////////////////// // PulseAudio requires a chain of callbacks with C linkage extern "C" { void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata); void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata); void callback_context_state(pa_context *context, void *userdata); } class LinuxVolumeCatcherImpl { public: LinuxVolumeCatcherImpl(); ~LinuxVolumeCatcherImpl(); void setVol(F32 volume); void pump(void); private: void init(); void cleanup(); void connected_okay(); void update_index_volume(F32 volume); void update_all_volumes(F32 volume); F32 mDesiredVolume; pa_glib_mainloop *mMainloop; pa_context *mPAContext; bool connected; std::set mSinkInputIndices; std::map mSinkInputNumChannels; }; LinuxVolumeCatcherImpl::LinuxVolumeCatcherImpl() : mDesiredVolume(0.0f), mMainloop(NULL), mPAContext(NULL), mConnected(false) { init(); } LinuxVolumeCatcherImpl::~LinuxVolumeCatcherImpl() { cleanup(); } LinuxVolumeCatcherImpl::init() { // try to be as robust as possible because PA's interface is a // bit fragile and (for our purposes) we'd rather simply not function // than crash mMainloop = pa_glib_mainloop_new(g_main_context_default()); if (mMainloop) { pa_mainloop_api *api = pa_glib_mainloop_get_api(mMainloop); if (api) { pa_proplist *proplist = pa_proplist_new(); if (proplist) { pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster"); pa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1"); // plain old pa_context_new() is broken! mPAContext = pa_context_new_with_proplist(api, NULL, proplist); pa_proplist_free(proplist); } } } // Now we've set up a PA context and mainloop, try connecting the // PA context to a PA daemon. if (mPAContext) { pa_context_set_state_callback(mPAContext, callback_context_state, NULL); pa_context_flags_t cflags = 0; // maybe add PA_CONTEXT_NOAUTOSPAWN? if (pa_context_connect(mPAContext, NULL, cflags, NULL) >= 0) { // Okay! We haven't definitely connected, but we // haven't definitely failed yet. } else { // Failed to connect to PA manager... we'll leave // things like that. Perhaps we should try again later. } } } LinuxVolumeCatcherImpl::cleanup() { // there's some cleanup we could do, but do nothing... for now. } LinuxVolumeCatcherImpl::setVol(F32 volume) { mDesiredVolume = volume; if (mConnected && mPAContext) { update_all_volumes(mDesiredVolume); } pump(); } LinuxVolumeCatcherImpl::pump() { g_main_context_iteration(g_main_context_default()); } void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata) { } void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata) { } void callback_context_state(pa_context *context, void *userdata) { }