Break Linux? Fix volume catcher? Only time will tell
This commit is contained in:
@@ -155,55 +155,40 @@ extern "C" {
|
||||
}
|
||||
|
||||
|
||||
class VolumeCatcherImpl
|
||||
class VolumeCatcherImpl : public LLSingleton<VolumeCatcherImpl>
|
||||
{
|
||||
public:
|
||||
friend LLSingleton<VolumeCatcherImpl>;
|
||||
VolumeCatcherImpl();
|
||||
// This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance.
|
||||
~VolumeCatcherImpl();
|
||||
|
||||
public:
|
||||
|
||||
void setVolume(F32 volume);
|
||||
void pump(void);
|
||||
void setPan(F32 pan) {};
|
||||
|
||||
// for internal use - can't be private because used from our C callbacks
|
||||
|
||||
bool loadsyms(std::string pulse_dso_name);
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
void update_all_volumes(F32 volume);
|
||||
void update_index_volume(U32 index, F32 volume);
|
||||
void connected_okay();
|
||||
|
||||
std::set<U32> mSinkInputIndices;
|
||||
std::map<U32,U32> mSinkInputNumChannels;
|
||||
F32 mDesiredVolume;
|
||||
F32 mVolume;
|
||||
pa_glib_mainloop *mMainloop;
|
||||
pa_context *mPAContext;
|
||||
bool mConnected;
|
||||
bool mGotSyms;
|
||||
F32 mPan;
|
||||
};
|
||||
|
||||
VolumeCatcherImpl::VolumeCatcherImpl()
|
||||
: mDesiredVolume(0.0f),
|
||||
mMainloop(NULL),
|
||||
mPAContext(NULL),
|
||||
: mVolume(0.0f),
|
||||
mMainloop(nullptr),
|
||||
mPAContext(nullptr),
|
||||
mConnected(false),
|
||||
mGotSyms(false)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
VolumeCatcherImpl::~VolumeCatcherImpl()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name)
|
||||
{
|
||||
return grab_pa_syms(pulse_dso_name);
|
||||
}
|
||||
|
||||
void VolumeCatcherImpl::init()
|
||||
mGotSyms(false),
|
||||
mPan(0.f) // default pan is centered
|
||||
{
|
||||
// try to be as defensive as possible because PA's interface is a
|
||||
// bit fragile and (for our purposes) we'd rather simply not function
|
||||
@@ -213,7 +198,7 @@ void VolumeCatcherImpl::init()
|
||||
// libpulse.so.0 - this isn't a great assumption, and the two DSOs should
|
||||
// probably be loaded separately. Our Linux DSO framework needs refactoring,
|
||||
// we do this sort of thing a lot with practically identical logic...
|
||||
mGotSyms = loadsyms("libpulse-mainloop-glib.so.0");
|
||||
mGotSyms = grab_pa_syms("libpulse-mainloop-glib.so.0");
|
||||
if (!mGotSyms) return;
|
||||
|
||||
// better make double-sure glib itself is initialized properly.
|
||||
@@ -245,7 +230,7 @@ void VolumeCatcherImpl::init()
|
||||
// PA context to a PA daemon.
|
||||
if (mPAContext)
|
||||
{
|
||||
llpa_context_set_state_callback(mPAContext, callback_context_state, this);
|
||||
llpa_context_set_state_callback(mPAContext, callback_context_state, nullptr);
|
||||
pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN?
|
||||
if (llpa_context_connect(mPAContext, NULL, cflags, NULL) >= 0)
|
||||
{
|
||||
@@ -260,7 +245,7 @@ void VolumeCatcherImpl::init()
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeCatcherImpl::cleanup()
|
||||
VolumeCatcherImpl::~VolumeCatcherImpl()
|
||||
{
|
||||
mConnected = false;
|
||||
|
||||
@@ -269,33 +254,30 @@ void VolumeCatcherImpl::cleanup()
|
||||
llpa_context_disconnect(mPAContext);
|
||||
llpa_context_unref(mPAContext);
|
||||
}
|
||||
mPAContext = NULL;
|
||||
mPAContext = nullptr;
|
||||
|
||||
if (mGotSyms && mMainloop)
|
||||
{
|
||||
llpa_glib_mainloop_free(mMainloop);
|
||||
}
|
||||
mMainloop = NULL;
|
||||
mMainloop = nullptr;
|
||||
}
|
||||
|
||||
void VolumeCatcherImpl::setVolume(F32 volume)
|
||||
{
|
||||
mDesiredVolume = volume;
|
||||
mVolume = volume;
|
||||
|
||||
if (!mGotSyms) return;
|
||||
|
||||
if (mConnected && mPAContext)
|
||||
{
|
||||
update_all_volumes(mDesiredVolume);
|
||||
for (auto& input : mSinkInputIndices)
|
||||
{
|
||||
update_index_volume(input, mVolume);
|
||||
}
|
||||
}
|
||||
|
||||
pump();
|
||||
}
|
||||
|
||||
void VolumeCatcherImpl::pump()
|
||||
{
|
||||
gboolean may_block = FALSE;
|
||||
g_main_context_iteration(g_main_context_default(), may_block);
|
||||
g_main_context_iteration(g_main_context_default(), /*may_block = */false);
|
||||
}
|
||||
|
||||
void VolumeCatcherImpl::connected_okay()
|
||||
@@ -305,7 +287,7 @@ void VolumeCatcherImpl::connected_okay()
|
||||
// fetch global list of existing sinkinputs
|
||||
if ((op = llpa_context_get_sink_input_info_list(mPAContext,
|
||||
callback_discovered_sinkinput,
|
||||
this)))
|
||||
nullptr)))
|
||||
{
|
||||
llpa_operation_unref(op);
|
||||
}
|
||||
@@ -313,7 +295,7 @@ void VolumeCatcherImpl::connected_okay()
|
||||
// subscribe to future global sinkinput changes
|
||||
llpa_context_set_subscribe_callback(mPAContext,
|
||||
callback_subscription_alert,
|
||||
this);
|
||||
nullptr);
|
||||
if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t)
|
||||
(PA_SUBSCRIPTION_MASK_SINK_INPUT),
|
||||
NULL, NULL)))
|
||||
@@ -322,15 +304,6 @@ void VolumeCatcherImpl::connected_okay()
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeCatcherImpl::update_all_volumes(F32 volume)
|
||||
{
|
||||
for (std::set<U32>::iterator it = mSinkInputIndices.begin();
|
||||
it != mSinkInputIndices.end(); ++it)
|
||||
{
|
||||
update_index_volume(*it, volume);
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume)
|
||||
{
|
||||
static pa_cvolume cvol;
|
||||
@@ -340,10 +313,10 @@ void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume)
|
||||
pa_context *c = mPAContext;
|
||||
uint32_t idx = index;
|
||||
const pa_cvolume *cvolumep = &cvol;
|
||||
pa_context_success_cb_t cb = NULL; // okay as null
|
||||
void *userdata = NULL; // okay as null
|
||||
pa_context_success_cb_t cb = nullptr; // okay as null
|
||||
void* userdata = nullptr; // okay as null
|
||||
|
||||
pa_operation *op;
|
||||
pa_operation* op;
|
||||
if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata)))
|
||||
{
|
||||
llpa_operation_unref(op);
|
||||
@@ -353,9 +326,6 @@ void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume)
|
||||
|
||||
void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata)
|
||||
{
|
||||
VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
|
||||
llassert(impl);
|
||||
|
||||
if (0 == eol)
|
||||
{
|
||||
pa_proplist *proplist = sii->proplist;
|
||||
@@ -363,6 +333,7 @@ void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info
|
||||
|
||||
if (sinkpid == getpid()) // does the discovered sinkinput belong to this process?
|
||||
{
|
||||
auto impl = VolumeCatcherImpl::getInstance();
|
||||
bool is_new = (impl->mSinkInputIndices.find(sii->index) ==
|
||||
impl->mSinkInputIndices.end());
|
||||
|
||||
@@ -371,12 +342,7 @@ void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info
|
||||
|
||||
if (is_new)
|
||||
{
|
||||
// new!
|
||||
impl->update_index_volume(sii->index, impl->mDesiredVolume);
|
||||
}
|
||||
else
|
||||
{
|
||||
// seen it already, do nothing.
|
||||
impl->update_index_volume(sii->index, impl->mVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,8 +350,7 @@ void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info
|
||||
|
||||
void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata)
|
||||
{
|
||||
VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
|
||||
llassert(impl);
|
||||
VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
|
||||
|
||||
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
|
||||
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
|
||||
@@ -401,15 +366,11 @@ void callback_subscription_alert(pa_context *context, pa_subscription_event_type
|
||||
{
|
||||
// ask for more info about this new sinkinput
|
||||
pa_operation *op;
|
||||
if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl)))
|
||||
if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, nullptr)))
|
||||
{
|
||||
llpa_operation_unref(op);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// property change on this sinkinput - we don't care.
|
||||
}
|
||||
break;
|
||||
|
||||
default:;
|
||||
@@ -418,8 +379,7 @@ void callback_subscription_alert(pa_context *context, pa_subscription_event_type
|
||||
|
||||
void callback_context_state(pa_context *context, void *userdata)
|
||||
{
|
||||
VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
|
||||
llassert(impl);
|
||||
VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
|
||||
|
||||
switch (llpa_context_get_state(context))
|
||||
{
|
||||
@@ -441,28 +401,25 @@ void callback_context_state(pa_context *context, void *userdata)
|
||||
|
||||
VolumeCatcher::VolumeCatcher()
|
||||
{
|
||||
pimpl = new VolumeCatcherImpl();
|
||||
pimpl = VolumeCatcherImpl::getInstance();
|
||||
}
|
||||
|
||||
VolumeCatcher::~VolumeCatcher()
|
||||
{
|
||||
delete pimpl;
|
||||
pimpl = NULL;
|
||||
// Let the instance persist until exit.
|
||||
}
|
||||
|
||||
void VolumeCatcher::setVolume(F32 volume)
|
||||
{
|
||||
llassert(pimpl);
|
||||
pimpl->setVolume(volume);
|
||||
}
|
||||
|
||||
void VolumeCatcher::setPan(F32 pan)
|
||||
{
|
||||
// TODO: implement this (if possible)
|
||||
pimpl->setPan(pan);
|
||||
}
|
||||
|
||||
void VolumeCatcher::pump()
|
||||
{
|
||||
llassert(pimpl);
|
||||
pimpl->pump();
|
||||
g_main_context_iteration(g_main_context_default(), /*may_block = */false);
|
||||
}
|
||||
|
||||
@@ -45,67 +45,47 @@
|
||||
|
||||
struct VolumeCatcherStorage;
|
||||
|
||||
class VolumeCatcherImpl
|
||||
class VolumeCatcherImpl : public LLSingleton<VolumeCatcherImpl>
|
||||
{
|
||||
friend LLSingleton<VolumeCatcherImpl>;
|
||||
VolumeCatcherImpl();
|
||||
// This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance.
|
||||
~VolumeCatcherImpl();
|
||||
|
||||
public:
|
||||
|
||||
void setVolume(F32 volume);
|
||||
void setPan(F32 pan);
|
||||
|
||||
void setInstanceVolume(VolumeCatcherStorage *instance);
|
||||
|
||||
std::list<VolumeCatcherStorage*> mComponentInstances;
|
||||
Component mOriginalDefaultOutput;
|
||||
Component mVolumeAdjuster;
|
||||
|
||||
static VolumeCatcherImpl *getInstance();
|
||||
|
||||
private:
|
||||
// This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance.
|
||||
VolumeCatcherImpl();
|
||||
static VolumeCatcherImpl *sInstance;
|
||||
|
||||
// The singlar instance of this class is expected to last until the process exits.
|
||||
// To ensure this, we declare the destructor here but never define it, so any code which attempts to destroy the instance will not link.
|
||||
~VolumeCatcherImpl();
|
||||
|
||||
F32 mVolume;
|
||||
F32 mPan;
|
||||
};
|
||||
|
||||
VolumeCatcherImpl *VolumeCatcherImpl::sInstance = NULL;;
|
||||
|
||||
struct VolumeCatcherStorage
|
||||
{
|
||||
ComponentInstance self;
|
||||
ComponentInstance delegate;
|
||||
ComponentInstance self, delegate;
|
||||
};
|
||||
|
||||
static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage);
|
||||
static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self);
|
||||
static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self);
|
||||
|
||||
VolumeCatcherImpl *VolumeCatcherImpl::getInstance()
|
||||
{
|
||||
if(!sInstance)
|
||||
{
|
||||
sInstance = new VolumeCatcherImpl;
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
VolumeCatcherImpl::VolumeCatcherImpl()
|
||||
: mVolume(1.0f), // default volume is max
|
||||
mPan(0.f) // default pan is centered
|
||||
{
|
||||
mVolume = 1.0; // default to full volume
|
||||
mPan = 0.0; // and center pan
|
||||
|
||||
ComponentDescription desc;
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
|
||||
// Find the original default output component
|
||||
mOriginalDefaultOutput = FindNextComponent(NULL, &desc);
|
||||
|
||||
@@ -114,7 +94,6 @@ VolumeCatcherImpl::VolumeCatcherImpl()
|
||||
|
||||
// Capture the original component, so we always get found instead.
|
||||
CaptureComponent(mOriginalDefaultOutput, mVolumeAdjuster);
|
||||
|
||||
}
|
||||
|
||||
static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage)
|
||||
@@ -146,20 +125,18 @@ static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, H
|
||||
|
||||
static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self)
|
||||
{
|
||||
ComponentResult result = noErr;
|
||||
VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
|
||||
|
||||
storage = new VolumeCatcherStorage;
|
||||
|
||||
storage->self = self;
|
||||
storage->delegate = NULL;
|
||||
storage->delegate = nullptr;
|
||||
|
||||
result = OpenAComponent(impl->mOriginalDefaultOutput, &(storage->delegate));
|
||||
ComponentResult result = OpenAComponent(impl->mOriginalDefaultOutput, &(storage->delegate));
|
||||
|
||||
if(result != noErr)
|
||||
{
|
||||
// std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl;
|
||||
|
||||
// If we failed to open the delagate component, our open is going to fail. Clean things up.
|
||||
delete storage;
|
||||
}
|
||||
@@ -172,7 +149,7 @@ static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *stora
|
||||
impl->mComponentInstances.push_back(storage);
|
||||
|
||||
// and set up the initial volume
|
||||
impl->setInstanceVolume(storage);
|
||||
impl->setVolume(storage);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -180,14 +157,12 @@ static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *stora
|
||||
|
||||
static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self)
|
||||
{
|
||||
ComponentResult result = noErr;
|
||||
|
||||
if(storage)
|
||||
{
|
||||
if(storage->delegate)
|
||||
{
|
||||
CloseComponent(storage->delegate);
|
||||
storage->delegate = NULL;
|
||||
storage->delegate = nullptr;
|
||||
}
|
||||
|
||||
VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
|
||||
@@ -195,18 +170,30 @@ static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *stor
|
||||
delete[] storage;
|
||||
}
|
||||
|
||||
return result;
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void VolumeCatcherImpl::setVolume(F32 volume)
|
||||
{
|
||||
VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
|
||||
impl->mVolume = volume;
|
||||
|
||||
mVolume = volume;
|
||||
|
||||
// Iterate through all known instances, setting the volume on each.
|
||||
for(std::list<VolumeCatcherStorage*>::iterator iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter)
|
||||
for(auto& instance : mComponentInstances)
|
||||
{
|
||||
impl->setInstanceVolume(*iter);
|
||||
// std::cerr << "Setting volume on component instance: " << (instance->delegate) << " to " << mVolume << std::endl;
|
||||
if (instance && instance->delegate)
|
||||
{
|
||||
if (OSStatus err = AudioUnitSetParameter(
|
||||
instance->delegate,
|
||||
kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
mVolume,
|
||||
0))
|
||||
{
|
||||
// std::cerr << " AudioUnitSetParameter returned " << err << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,35 +201,12 @@ void VolumeCatcherImpl::setPan(F32 pan)
|
||||
{
|
||||
VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
|
||||
impl->mPan = pan;
|
||||
|
||||
|
||||
// TODO: implement this.
|
||||
// This will probably require adding a "panner" audio unit to the chain somehow.
|
||||
// There's also a "3d mixer" component that we might be able to use...
|
||||
}
|
||||
|
||||
void VolumeCatcherImpl::setInstanceVolume(VolumeCatcherStorage *instance)
|
||||
{
|
||||
// std::cerr << "Setting volume on component instance: " << (instance->delegate) << " to " << mVolume << std::endl;
|
||||
|
||||
OSStatus err = noErr;
|
||||
|
||||
if(instance && instance->delegate)
|
||||
{
|
||||
err = AudioUnitSetParameter(
|
||||
instance->delegate,
|
||||
kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
mVolume,
|
||||
0);
|
||||
}
|
||||
|
||||
if(err)
|
||||
{
|
||||
// std::cerr << " AudioUnitSetParameter returned " << err << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
VolumeCatcher::VolumeCatcher()
|
||||
|
||||
Reference in New Issue
Block a user