Skip to content

Commit

Permalink
AESinkPulse: Implement Hotplug monitor
Browse files Browse the repository at this point in the history
  • Loading branch information
FernetMenta authored and fritsch committed Nov 8, 2019
2 parents a4dfad6 + 4315778 commit c8d97a8
Show file tree
Hide file tree
Showing 20 changed files with 298 additions and 124 deletions.
2 changes: 2 additions & 0 deletions xbmc/Application.cpp
Expand Up @@ -364,6 +364,8 @@ bool CApplication::Create(const CAppParamParser &params)
m_bTestMode = params.m_testmode;
m_bStandalone = params.m_standAlone;

CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo());

m_pSettingsComponent.reset(new CSettingsComponent());
m_pSettingsComponent->Init(params);

Expand Down
244 changes: 178 additions & 66 deletions xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp
Expand Up @@ -15,6 +15,28 @@
#include "utils/StringUtils.h"
#include "utils/log.h"

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

class CDriverMonitor
{
public:
CDriverMonitor() = default;
virtual ~CDriverMonitor();
bool Start();
bool IsInitialized();

CCriticalSection m_sec;

protected:
pa_context* m_pContext = nullptr;
pa_threaded_mainloop* m_pMainLoop = nullptr;
bool m_isInit = false;
};

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

static const char *ContextStateToString(pa_context_state s)
{
switch (s)
Expand Down Expand Up @@ -206,6 +228,42 @@ static void SinkInputInfoCallback(pa_context *c, const pa_sink_input_info *i, in
p->UpdateInternalVolume(&(i->volume));
}

static void SinkCallback(pa_context* c,
pa_subscription_event_type_t t,
uint32_t idx,
void* userdata)
{
CDriverMonitor* p = static_cast<CDriverMonitor*>(userdata);
if (!p)
return;

CSingleLock lock(p->m_sec);
if (p->IsInitialized())
{
if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
{
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW)
{
CLog::Log(LOGDEBUG, "Sink appeared");
CServiceBroker::GetActiveAE()->DeviceChange();
}
else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
{
CLog::Log(LOGDEBUG, "Sink removed");
CServiceBroker::GetActiveAE()->DeviceChange();
}
else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE)
{
CLog::Log(LOGDEBUG, "Sink changed");
}
}
else
{
CLog::Log(LOGDEBUG, "Not subscribed to Event: %d", static_cast<int>(t));
}
}
}

static void SinkChangedCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
{
CAESinkPULSE* p = static_cast<CAESinkPULSE*>(userdata);
Expand All @@ -232,23 +290,6 @@ static void SinkChangedCallback(pa_context *c, pa_subscription_event_type_t t, u
pa_operation_unref(op);
}
}
else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
{
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW)
{
CLog::Log(LOGDEBUG, "Sink appeared");
CServiceBroker::GetActiveAE()->DeviceChange();
}
else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
{
CLog::Log(LOGDEBUG, "Sink removed");
CServiceBroker::GetActiveAE()->DeviceChange();
}
else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE)
{
CLog::Log(LOGDEBUG, "Sink changed");
}
}
else
{
CLog::Log(LOGDEBUG, "Not subscribed to Event: %d", static_cast<int>(t));
Expand Down Expand Up @@ -497,8 +538,119 @@ static void SinkInfoRequestCallback(pa_context *c, const pa_sink_info *i, int eo
pa_threaded_mainloop_signal(sinkStruct->mainloop, 0);
}

static bool SetupContext(const char* host,
const char* appname,
pa_context** context,
pa_threaded_mainloop** mainloop)
{
if ((*mainloop = pa_threaded_mainloop_new()) == nullptr)
{
CLog::Log(LOGERROR, "PulseAudio: Failed to allocate main loop");
return false;
}

if (((*context) = pa_context_new(pa_threaded_mainloop_get_api(*mainloop), appname)) == nullptr)
{
CLog::Log(LOGERROR, "PulseAudio: Failed to allocate context");
return false;
}

pa_context_set_state_callback(*context, ContextStateCallback, *mainloop);

if (pa_context_connect(*context, host, (pa_context_flags_t)0, nullptr) < 0)
{
CLog::Log(LOGERROR, "PulseAudio: Failed to connect context");
return false;
}
pa_threaded_mainloop_lock(*mainloop);

if (pa_threaded_mainloop_start(*mainloop) < 0)
{
CLog::Log(LOGERROR, "PulseAudio: Failed to start MainLoop");
pa_threaded_mainloop_unlock(*mainloop);
return false;
}

/* Wait until the context is ready */
do
{
pa_threaded_mainloop_wait(*mainloop);
CLog::Log(LOGDEBUG, "PulseAudio: Context %s",
ContextStateToString(pa_context_get_state(*context)));
} while (pa_context_get_state(*context) != PA_CONTEXT_READY &&
pa_context_get_state(*context) != PA_CONTEXT_FAILED);

if (pa_context_get_state(*context) == PA_CONTEXT_FAILED)
{
CLog::Log(LOGERROR, "PulseAudio: Waited for the Context but it failed");
pa_threaded_mainloop_unlock(*mainloop);
return false;
}

pa_threaded_mainloop_unlock(*mainloop);
return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

CDriverMonitor::~CDriverMonitor()
{
m_isInit = false;

if (m_pMainLoop)
pa_threaded_mainloop_stop(m_pMainLoop);

if (m_pContext)
{
pa_context_disconnect(m_pContext);
pa_context_unref(m_pContext);
m_pContext = nullptr;
}

if (m_pMainLoop)
{
pa_threaded_mainloop_free(m_pMainLoop);
m_pMainLoop = nullptr;
}
}

bool CDriverMonitor::IsInitialized()
{
return m_isInit;
}

bool CDriverMonitor::Start()
{
if (!SetupContext(nullptr, "KodiDriver", &m_pContext, &m_pMainLoop))
{
CLog::Log(LOGNOTICE, "PulseAudio might not be running. Context was not created.");
return false;
}

pa_threaded_mainloop_lock(m_pMainLoop);

m_isInit = true;

CSingleLock lock(m_sec);
// Register Callback for Sink changes
pa_context_set_subscribe_callback(m_pContext, SinkCallback, this);
const pa_subscription_mask_t mask = pa_subscription_mask_t(PA_SUBSCRIPTION_MASK_SINK);
pa_operation* op = pa_context_subscribe(m_pContext, mask, nullptr, this);
if (op != nullptr)
pa_operation_unref(op);

pa_threaded_mainloop_unlock(m_pMainLoop);

return true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

/* PulseAudio class memberfunctions*/

std::unique_ptr<CDriverMonitor> CAESinkPULSE::m_pMonitor;

bool CAESinkPULSE::Register()
{
// check if pulseaudio is actually available
Expand All @@ -519,10 +671,14 @@ bool CAESinkPULSE::Register()
pa_simple_free(s);
}

m_pMonitor.reset(new CDriverMonitor());
m_pMonitor->Start();

AE::AESinkRegEntry entry;
entry.sinkName = "PULSE";
entry.createFunc = CAESinkPULSE::Create;
entry.enumerateFunc = CAESinkPULSE::EnumerateDevicesEx;
entry.cleanupFunc = CAESinkPULSE::Cleanup;
AE::CAESinkFactory::RegisterSink(entry);
return true;
}
Expand Down Expand Up @@ -571,7 +727,7 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device)
m_Context = NULL;
m_periodSize = 0;

if (!SetupContext(NULL, &m_Context, &m_MainLoop))
if (!SetupContext(NULL, "KodiSink", &m_Context, &m_MainLoop))
{
CLog::Log(LOGNOTICE, "PulseAudio might not be running. Context was not created.");
Deinitialize();
Expand Down Expand Up @@ -816,7 +972,7 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device)
CSingleLock lock(m_sec);
// Register Callback for Sink changes
pa_context_set_subscribe_callback(m_Context, SinkChangedCallback, this);
const pa_subscription_mask_t mask = pa_subscription_mask_t (PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SINK_INPUT);
const pa_subscription_mask_t mask = pa_subscription_mask_t(PA_SUBSCRIPTION_MASK_SINK_INPUT);
pa_operation *op = pa_context_subscribe(m_Context, mask, NULL, this);
if (op != NULL)
pa_operation_unref(op);
Expand Down Expand Up @@ -1013,7 +1169,7 @@ void CAESinkPULSE::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
pa_context *context;
pa_threaded_mainloop *mainloop;

if (!SetupContext(NULL, &context, &mainloop))
if (!SetupContext(NULL, "KodiSink", &context, &mainloop))
{
CLog::Log(LOGNOTICE, "PulseAudio might not be running. Context was not created.");
return;
Expand Down Expand Up @@ -1093,51 +1249,7 @@ inline bool CAESinkPULSE::WaitForOperation(pa_operation *op, pa_threaded_mainloo
return success;
}

bool CAESinkPULSE::SetupContext(const char *host, pa_context **context, pa_threaded_mainloop **mainloop)
void CAESinkPULSE::Cleanup()
{
if ((*mainloop = pa_threaded_mainloop_new()) == NULL)
{
CLog::Log(LOGERROR, "PulseAudio: Failed to allocate main loop");
return false;
}

if (((*context) = pa_context_new(pa_threaded_mainloop_get_api(*mainloop), "Kodi")) == NULL)
{
CLog::Log(LOGERROR, "PulseAudio: Failed to allocate context");
return false;
}

pa_context_set_state_callback(*context, ContextStateCallback, *mainloop);

if (pa_context_connect(*context, host, (pa_context_flags_t)0, NULL) < 0)
{
CLog::Log(LOGERROR, "PulseAudio: Failed to connect context");
return false;
}
pa_threaded_mainloop_lock(*mainloop);

if (pa_threaded_mainloop_start(*mainloop) < 0)
{
CLog::Log(LOGERROR, "PulseAudio: Failed to start MainLoop");
pa_threaded_mainloop_unlock(*mainloop);
return false;
}

/* Wait until the context is ready */
do
{
pa_threaded_mainloop_wait(*mainloop);
CLog::Log(LOGDEBUG, "PulseAudio: Context %s", ContextStateToString(pa_context_get_state(*context)));
}
while (pa_context_get_state(*context) != PA_CONTEXT_READY && pa_context_get_state(*context) != PA_CONTEXT_FAILED);

if (pa_context_get_state(*context) == PA_CONTEXT_FAILED)
{
CLog::Log(LOGERROR, "PulseAudio: Waited for the Context but it failed");
pa_threaded_mainloop_unlock(*mainloop);
return false;
}

pa_threaded_mainloop_unlock(*mainloop);
return true;
m_pMonitor.reset();
}
8 changes: 7 additions & 1 deletion xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h
Expand Up @@ -13,9 +13,13 @@
#include "cores/AudioEngine/Utils/AEUtil.h"
#include "threads/CriticalSection.h"

#include <memory>

#include <pulse/pulseaudio.h>
#include <pulse/simple.h>

class CDriverMonitor;

class CAESinkPULSE : public IAESink
{
public:
Expand All @@ -27,6 +31,7 @@ class CAESinkPULSE : public IAESink
static bool Register();
static IAESink* Create(std::string &device, AEAudioFormat &desiredFormat);
static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force = false);
static void Cleanup();

bool Initialize(AEAudioFormat &format, std::string &device) override;
void Deinitialize() override;
Expand All @@ -47,7 +52,6 @@ class CAESinkPULSE : public IAESink
private:
void Pause(bool pause);
static inline bool WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry);
static bool SetupContext(const char *host, pa_context **context, pa_threaded_mainloop **mainloop);

bool m_IsAllocated;
bool m_passthrough;
Expand All @@ -65,4 +69,6 @@ class CAESinkPULSE : public IAESink

pa_context *m_Context;
pa_threaded_mainloop *m_MainLoop;

static std::unique_ptr<CDriverMonitor> m_pMonitor;
};
2 changes: 0 additions & 2 deletions xbmc/platform/Platform.cpp
Expand Up @@ -9,7 +9,6 @@
#include "Platform.h"

#include "ServiceBroker.h"
#include "utils/CPUInfo.h"

// Override for platform ports
#if !defined(PLATFORM_OVERRIDE)
Expand All @@ -29,6 +28,5 @@ CPlatform::~CPlatform() = default;

void CPlatform::Init()
{
CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo());
}

5 changes: 3 additions & 2 deletions xbmc/platform/android/CPUInfoAndroid.h
Expand Up @@ -8,10 +8,11 @@

#pragma once

#include "utils/CPUInfo.h"
#include "utils/Temperature.h"

class CCPUInfoAndroid : public CCPUInfo
#include "platform/posix/CPUInfoPosix.h"

class CCPUInfoAndroid : public CCPUInfoPosix
{
public:
CCPUInfoAndroid();
Expand Down

0 comments on commit c8d97a8

Please sign in to comment.