From 77c0479f0dfc9de8cc43071f2393b6a29677a57a Mon Sep 17 00:00:00 2001 From: Rainer Hochecker Date: Fri, 8 Nov 2019 14:52:49 +0100 Subject: [PATCH 1/6] AESinkPULSE: Implement Hotplug monitor --- xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 244 ++++++++++++++----- xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h | 8 +- 2 files changed, 185 insertions(+), 67 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp index 139352d4bbf22..9f5bc275bdd59 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp @@ -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) @@ -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(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(t)); + } + } +} + static void SinkChangedCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { CAESinkPULSE* p = static_cast(userdata); @@ -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(t)); @@ -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 CAESinkPULSE::m_pMonitor; + bool CAESinkPULSE::Register() { // check if pulseaudio is actually available @@ -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; } @@ -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(); @@ -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); @@ -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; @@ -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(); } diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h index 21c97f3bbaebc..30e7eac9136c7 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h @@ -13,9 +13,13 @@ #include "cores/AudioEngine/Utils/AEUtil.h" #include "threads/CriticalSection.h" +#include + #include #include +class CDriverMonitor; + class CAESinkPULSE : public IAESink { public: @@ -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; @@ -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; @@ -65,4 +69,6 @@ class CAESinkPULSE : public IAESink pa_context *m_Context; pa_threaded_mainloop *m_MainLoop; + + static std::unique_ptr m_pMonitor; }; From a614eae0252542d437cd3235fd4d42028100b36a Mon Sep 17 00:00:00 2001 From: fritsch Date: Sat, 9 Nov 2019 12:25:17 +0100 Subject: [PATCH 2/6] AESinkPULSE: Distinguish HW, BT and Network device --- xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 35 +++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp index 9f5bc275bdd59..90498492b48f5 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp @@ -301,6 +301,8 @@ struct SinkInfoStruct { AEDeviceInfoList *list; bool isHWDevice; + bool isNWDevice; + bool isBTDevice; bool device_found; pa_threaded_mainloop *mainloop; int samplerate; @@ -309,6 +311,8 @@ struct SinkInfoStruct { list = nullptr; isHWDevice = false; + isNWDevice = false; + isBTDevice = false; device_found = true; mainloop = NULL; //called into C samplerate = 0; @@ -335,12 +339,23 @@ static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void if(i) { - if (i->flags && (i->flags & PA_SINK_HARDWARE)) - sinkStruct->isHWDevice = true; + if (i->flags) + { + if (i->flags & PA_SINK_HARDWARE) + sinkStruct->isHWDevice = true; + + if (i->flags & PA_SINK_NETWORK) + sinkStruct->isNWDevice = true; - sinkStruct->samplerate = i->sample_spec.rate; - sinkStruct->device_found = true; - sinkStruct->map = i->channel_map; + sinkStruct->isBTDevice = + StringUtils::EndsWithNoCase(std::string(i->name), std::string("a2dp_sink")); + if (sinkStruct->isBTDevice) + CLog::Log(LOGNOTICE, "Found BT Device - will adjust buffers to larger values"); + + sinkStruct->samplerate = i->sample_spec.rate; + sinkStruct->device_found = true; + sinkStruct->map = i->channel_map; + } } pa_threaded_mainloop_signal(sinkStruct->mainloop, 0); } @@ -900,7 +915,7 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) // align with AE's max buffer unsigned int latency = m_BytesPerSecond / 2.5; // 400 ms unsigned int process_time = latency / 4; // 100 ms - if(sinkStruct.isHWDevice) + if (sinkStruct.isHWDevice && !sinkStruct.isNWDevice && !sinkStruct.isBTDevice) { // on hw devices buffers can be further reduced // 200ms max latency @@ -984,9 +999,11 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) m_format = format; format.m_dataFormat = m_passthrough ? AE_FMT_S16NE : format.m_dataFormat; - CLog::Log(LOGNOTICE, "PulseAudio: Opened device %s in %s mode with Buffersize %u ms", - device.c_str(), m_passthrough ? "passthrough" : "pcm", - (unsigned int) ((m_BufferSize / (float) m_BytesPerSecond) * 1000)); + CLog::Log(LOGNOTICE, + "PulseAudio: Opened device %s in %s mode with Buffersize %u ms Periodsize %u ms", + device.c_str(), m_passthrough ? "passthrough" : "pcm", + static_cast(1000.0 * m_BufferSize / m_BytesPerSecond), + static_cast(1000.0 * m_periodSize / m_BytesPerSecond)); // Cork stream will resume when adding first package Pause(true); From 793d1b5590f100244b502e5326b02b44f50a34bd Mon Sep 17 00:00:00 2001 From: fritsch Date: Sat, 9 Nov 2019 14:52:12 +0100 Subject: [PATCH 3/6] AESinkPULSE: Fixup blocked write to use RequestCallback --- xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 50 ++++++++++++++++---- xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h | 7 +++ 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp index 90498492b48f5..4693ca5c12bd6 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp @@ -207,7 +207,14 @@ static void StreamStateCallback(pa_stream *s, void *userdata) static void StreamRequestCallback(pa_stream *s, size_t length, void *userdata) { - pa_threaded_mainloop* m = static_cast(userdata); + CAESinkPULSE* p = static_cast(userdata); + if (!p) + return; + + pa_threaded_mainloop* m = p->GetInternalMainLoop(); + // pulse always tells us the total number of bytes + // we can add. + p->m_requestedBytes = static_cast(length); pa_threaded_mainloop_signal(m, 0); } @@ -720,6 +727,7 @@ CAESinkPULSE::CAESinkPULSE() m_IsStreamPaused = false; m_volume_needs_update = false; m_periodSize = 0; + m_requestedBytes = 0; pa_cvolume_init(&m_Volume); } @@ -908,7 +916,7 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) } pa_stream_set_state_callback(m_Stream, StreamStateCallback, m_MainLoop); - pa_stream_set_write_callback(m_Stream, StreamRequestCallback, m_MainLoop); + pa_stream_set_write_callback(m_Stream, StreamRequestCallback, this); pa_stream_set_latency_update_callback(m_Stream, StreamLatencyUpdateCallback, m_MainLoop); // default buffer construction @@ -1011,7 +1019,6 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) CSingleLock lock(m_sec); m_IsAllocated = true; } - return true; } @@ -1021,6 +1028,7 @@ void CAESinkPULSE::Deinitialize() m_IsAllocated = false; m_passthrough = false; m_periodSize = 0; + m_requestedBytes = 0; if (m_Stream) Drain(); @@ -1090,22 +1098,39 @@ unsigned int CAESinkPULSE::AddPackets(uint8_t **data, unsigned int frames, unsig pa_threaded_mainloop_lock(m_MainLoop); unsigned int available = frames * m_format.m_frameSize; - unsigned int length = 0; + unsigned int length = m_periodSize; void *buffer = data[0]+offset*m_format.m_frameSize; - // care a bit for fragmentation - while ((length = pa_stream_writable_size(m_Stream)) < m_periodSize) + unsigned int wait_time = static_cast(1000.0 * m_BufferSize / m_BytesPerSecond); + m_extTimer.Set(wait_time); + // we don't want to block forever - if timer expires pa_stream_write will + // fail - therefore we don't care and just return 0; + while (!m_extTimer.IsTimePast()) + { + if (m_requestedBytes > 0) + break; pa_threaded_mainloop_wait(m_MainLoop); + } - length = std::min(length, available); + if (m_extTimer.IsTimePast()) + { + CLog::Log(LOGERROR, "Sink Timer expired for more than buffer time: %ul", wait_time); + pa_threaded_mainloop_unlock(m_MainLoop); + return 0; + } + length = std::min(length, available); int error = pa_stream_write(m_Stream, buffer, length, NULL, 0, PA_SEEK_RELATIVE); pa_threaded_mainloop_unlock(m_MainLoop); - if (error) { - CLog::Log(LOGERROR, "CPulseAudioDirectSound::AddPackets - pa_stream_write failed\n"); + CLog::Log(LOGERROR, "CAESinkPULSE::AddPackets - pa_stream_write failed: %d", error); return 0; } + + // subtract here, as we might come back earlier than our callback and there is + // still space in the buffer to write another time + m_requestedBytes -= length; + unsigned int res = length / m_format.m_frameSize; return res; @@ -1128,6 +1153,13 @@ pa_stream* CAESinkPULSE::GetInternalStream() return m_Stream; } +// This is a helper to use the internal mainloop from another thread, e.g. a RequestCallback +// it is shipped via the userdata. Don't use it for other purposes than signalling +pa_threaded_mainloop* CAESinkPULSE::GetInternalMainLoop() +{ + return m_MainLoop; +} + void CAESinkPULSE::UpdateInternalVolume(const pa_cvolume* nVol) { if (!nVol) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h index 30e7eac9136c7..0ece3b4910a9f 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h @@ -12,7 +12,9 @@ #include "cores/AudioEngine/Utils/AEDeviceInfo.h" #include "cores/AudioEngine/Utils/AEUtil.h" #include "threads/CriticalSection.h" +#include "threads/Thread.h" +#include #include #include @@ -48,7 +50,10 @@ class CAESinkPULSE : public IAESink bool IsInitialized(); void UpdateInternalVolume(const pa_cvolume* nVol); pa_stream* GetInternalStream(); + pa_threaded_mainloop* GetInternalMainLoop(); CCriticalSection m_sec; + std::atomic m_requestedBytes; + private: void Pause(bool pause); static inline bool WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry); @@ -70,5 +75,7 @@ class CAESinkPULSE : public IAESink pa_context *m_Context; pa_threaded_mainloop *m_MainLoop; + XbmcThreads::EndTime m_extTimer; + static std::unique_ptr m_pMonitor; }; From b16fdf7e904fc87391d32a9d23f3f3ecb5f7a502 Mon Sep 17 00:00:00 2001 From: Rainer Hochecker Date: Sun, 10 Nov 2019 11:50:44 +0100 Subject: [PATCH 4/6] AE: Implement selective device re-enumeration --- xbmc/cores/AudioEngine/AESinkFactory.cpp | 5 +- xbmc/cores/AudioEngine/AESinkFactory.h | 2 +- .../AudioEngine/Engines/ActiveAE/ActiveAE.cpp | 39 +++++++++++++-- .../AudioEngine/Engines/ActiveAE/ActiveAE.h | 2 + .../Engines/ActiveAE/ActiveAESink.cpp | 47 ++++++++++++++++--- .../Engines/ActiveAE/ActiveAESink.h | 5 +- xbmc/cores/AudioEngine/Interfaces/AE.h | 5 ++ xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 4 +- xbmc/utils/ActorProtocol.cpp | 13 +++-- xbmc/utils/ActorProtocol.h | 13 +++-- 10 files changed, 114 insertions(+), 21 deletions(-) diff --git a/xbmc/cores/AudioEngine/AESinkFactory.cpp b/xbmc/cores/AudioEngine/AESinkFactory.cpp index 64171fcc6bf74..5356359c4a4c1 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.cpp +++ b/xbmc/cores/AudioEngine/AESinkFactory.cpp @@ -85,12 +85,15 @@ IAESink *CAESinkFactory::Create(std::string &device, AEAudioFormat &desiredForma return nullptr; } -void CAESinkFactory::EnumerateEx(std::vector &list, bool force) +void CAESinkFactory::EnumerateEx(std::vector& list, bool force, std::string driver) { AESinkInfo info; for(auto reg : m_AESinkRegEntry) { + if (!driver.empty() && driver != reg.second.sinkName) + continue; + info.m_deviceInfoList.clear(); info.m_sinkName = reg.second.sinkName; reg.second.enumerateFunc(info.m_deviceInfoList, force); diff --git a/xbmc/cores/AudioEngine/AESinkFactory.h b/xbmc/cores/AudioEngine/AESinkFactory.h index 26e56a51422eb..2c5d62177a8fd 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.h +++ b/xbmc/cores/AudioEngine/AESinkFactory.h @@ -48,7 +48,7 @@ class CAESinkFactory static void ParseDevice(std::string &device, std::string &driver); static IAESink *Create(std::string &device, AEAudioFormat &desiredFormat); - static void EnumerateEx(std::vector &list, bool force); + static void EnumerateEx(std::vector& list, bool force, std::string driver); static void Cleanup(); protected: diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp index 917771b27b7ea..9211b4f5f58da 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp @@ -448,6 +448,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) return; case CActiveAEControlProtocol::DEVICECHANGE: + case CActiveAEControlProtocol::DEVICECOUNTCHANGE: LoadSettings(); if (!m_settings.device.empty() && CAESinkFactory::HasSinks()) { @@ -494,7 +495,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) { case CActiveAEControlProtocol::INIT: m_extError = false; - m_sink.EnumerateSinkList(false); + m_sink.EnumerateSinkList(false, ""); LoadSettings(); Configure(); if (!m_isWinSysReg) @@ -622,7 +623,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) m_extLastDeviceChange.push(now); UnconfigureSink(); m_controlPort.PurgeOut(CActiveAEControlProtocol::DEVICECHANGE); - m_sink.EnumerateSinkList(true); + m_sink.EnumerateSinkList(true, ""); LoadSettings(); m_extError = false; Configure(); @@ -637,6 +638,29 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) m_extTimeout = 500; } return; + case CActiveAEControlProtocol::DEVICECOUNTCHANGE: + const char* param; + param = reinterpret_cast(msg->data); + CLog::Log(LOGDEBUG, "CActiveAE - device count change event from driver: %s", param); + m_sink.EnumerateSinkList(true, param); + if (!m_sink.DeviceExist(m_settings.driver, m_currDevice)) + { + UnconfigureSink(); + LoadSettings(); + m_extError = false; + Configure(); + if (!m_extError) + { + m_state = AE_TOP_CONFIGURED_PLAY; + m_extTimeout = 0; + } + else + { + m_state = AE_TOP_ERROR; + m_extTimeout = 500; + } + } + return; case CActiveAEControlProtocol::PAUSESTREAM: CActiveAEStream *stream; stream = *(CActiveAEStream**)msg->data; @@ -835,7 +859,8 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) if (!displayReset) { m_controlPort.PurgeOut(CActiveAEControlProtocol::DEVICECHANGE); - m_sink.EnumerateSinkList(true); + m_controlPort.PurgeOut(CActiveAEControlProtocol::DEVICECOUNTCHANGE); + m_sink.EnumerateSinkList(true, ""); LoadSettings(); } Configure(); @@ -855,6 +880,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) m_extDeferData = false; return; case CActiveAEControlProtocol::DEVICECHANGE: + case CActiveAEControlProtocol::DEVICECOUNTCHANGE: return; default: break; @@ -2861,6 +2887,13 @@ void CActiveAE::DeviceChange() m_controlPort.SendOutMessage(CActiveAEControlProtocol::DEVICECHANGE); } +void CActiveAE::DeviceCountChange(std::string driver) +{ + const char* name = driver.c_str(); + m_controlPort.SendOutMessage(CActiveAEControlProtocol::DEVICECOUNTCHANGE, name, + driver.length() + 1); +} + bool CActiveAE::GetCurrentSinkFormat(AEAudioFormat &SinkFormat) { SinkFormat = m_stats.GetCurrentSinkFormat(); diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h index 6dddf7a97e2d8..14c99a079ec14 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h @@ -73,6 +73,7 @@ class CActiveAEControlProtocol : public Protocol RECONFIGURE, SUSPEND, DEVICECHANGE, + DEVICECOUNTCHANGE, MUTE, VOLUME, PAUSESTREAM, @@ -254,6 +255,7 @@ class CActiveAE : public IAE, public IDispResource, private CThread bool IsSettingVisible(const std::string &settingId) override; void KeepConfiguration(unsigned int millis) override; void DeviceChange() override; + void DeviceCountChange(std::string driver) override; bool GetCurrentSinkFormat(AEAudioFormat &SinkFormat) override; void RegisterAudioCallback(IAudioCallback* pCallback) override; diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp index d844f49babcab..803026e21e94f 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp @@ -205,6 +205,26 @@ bool CActiveAESink::NeedIECPacking() return true; } +bool CActiveAESink::DeviceExist(std::string driver, std::string device) +{ + if (driver.empty() && m_sink) + driver = m_sink->GetName(); + + for (const auto& itt : m_sinkInfoList) + { + if (itt.m_sinkName != driver) + continue; + + for (auto itt2 : itt.m_deviceInfoList) + { + CAEDeviceInfo& info = itt2; + if (info.m_deviceName == device) + return true; + } + } + return false; +} + enum SINK_STATES { S_TOP = 0, // 0 @@ -661,7 +681,7 @@ void CActiveAESink::Process() } } -void CActiveAESink::EnumerateSinkList(bool force) +void CActiveAESink::EnumerateSinkList(bool force, std::string driver) { if (!m_sinkInfoList.empty() && !force) return; @@ -669,25 +689,40 @@ void CActiveAESink::EnumerateSinkList(bool force) if (!CAESinkFactory::HasSinks()) return; + std::vector tmpList(m_sinkInfoList); + unsigned int c_retry = 4; m_sinkInfoList.clear(); - CAESinkFactory::EnumerateEx(m_sinkInfoList, false); + + if (!driver.empty()) + { + for (auto const& info : tmpList) + { + if (info.m_sinkName != driver) + m_sinkInfoList.push_back(info); + } + } + + CAESinkFactory::EnumerateEx(m_sinkInfoList, false, driver); while (m_sinkInfoList.empty() && c_retry > 0) { CLog::Log(LOGNOTICE, "No Devices found - retry: %d", c_retry); Sleep(1500); c_retry--; // retry the enumeration - CAESinkFactory::EnumerateEx(m_sinkInfoList, true); + CAESinkFactory::EnumerateEx(m_sinkInfoList, true, driver); } CLog::Log(LOGNOTICE, "Found %lu Lists of Devices", m_sinkInfoList.size()); - PrintSinks(); + PrintSinks(driver); } -void CActiveAESink::PrintSinks() +void CActiveAESink::PrintSinks(std::string& driver) { for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) { + if (!driver.empty() && itt->m_sinkName != driver) + continue; + CLog::Log(LOGNOTICE, "Enumerated %s devices:", itt->m_sinkName.c_str()); int count = 0; for (auto itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2) @@ -704,7 +739,7 @@ void CActiveAESink::PrintSinks() void CActiveAESink::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough) { - EnumerateSinkList(false); + EnumerateSinkList(false, ""); for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) { diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h index cbf5330d2d48a..da5433861da4c 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h @@ -83,20 +83,21 @@ class CActiveAESink : private CThread { public: explicit CActiveAESink(CEvent *inMsgEvent); - void EnumerateSinkList(bool force); + void EnumerateSinkList(bool force, std::string driver); void EnumerateOutputDevices(AEDeviceList &devices, bool passthrough); void Start(); void Dispose(); AEDeviceType GetDeviceType(const std::string &device); bool HasPassthroughDevice(); bool SupportsFormat(const std::string &device, AEAudioFormat &format); + bool DeviceExist(std::string driver, std::string device); CSinkControlProtocol m_controlPort; CSinkDataProtocol m_dataPort; protected: void Process() override; void StateMachine(int signal, Protocol *port, Message *msg); - void PrintSinks(); + void PrintSinks(std::string& driver); void GetDeviceFriendlyName(std::string &device); void OpenSink(); void ReturnBuffers(); diff --git a/xbmc/cores/AudioEngine/Interfaces/AE.h b/xbmc/cores/AudioEngine/Interfaces/AE.h index d6b08ed5ed752..63f0544181e38 100644 --- a/xbmc/cores/AudioEngine/Interfaces/AE.h +++ b/xbmc/cores/AudioEngine/Interfaces/AE.h @@ -222,6 +222,11 @@ class IAE */ virtual void DeviceChange() {} + /** + * Instruct AE to re-initialize, e.g. after ELD change event + */ + virtual void DeviceCountChange(std::string driver) {} + /** * Get the current sink data format * diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp index 4693ca5c12bd6..8ff62bb83b543 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp @@ -252,12 +252,12 @@ static void SinkCallback(pa_context* c, if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { CLog::Log(LOGDEBUG, "Sink appeared"); - CServiceBroker::GetActiveAE()->DeviceChange(); + CServiceBroker::GetActiveAE()->DeviceCountChange("PULSE"); } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { CLog::Log(LOGDEBUG, "Sink removed"); - CServiceBroker::GetActiveAE()->DeviceChange(); + CServiceBroker::GetActiveAE()->DeviceCountChange("PULSE"); } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) { diff --git a/xbmc/utils/ActorProtocol.cpp b/xbmc/utils/ActorProtocol.cpp index 6d504b7da8c97..f83d2fc61602e 100644 --- a/xbmc/utils/ActorProtocol.cpp +++ b/xbmc/utils/ActorProtocol.cpp @@ -117,7 +117,10 @@ void Protocol::ReturnMessage(Message *msg) freeMessageQueue.push(msg); } -bool Protocol::SendOutMessage(int signal, void *data /* = NULL */, size_t size /* = 0 */, Message *outMsg /* = NULL */) +bool Protocol::SendOutMessage(int signal, + const void* data /* = NULL */, + size_t size /* = 0 */, + Message* outMsg /* = NULL */) { Message *msg; if (outMsg) @@ -168,7 +171,10 @@ bool Protocol::SendOutMessage(int signal, CPayloadWrapBase *payload, Message *ou return true; } -bool Protocol::SendInMessage(int signal, void *data /* = NULL */, size_t size /* = 0 */, Message *outMsg /* = NULL */) +bool Protocol::SendInMessage(int signal, + const void* data /* = NULL */, + size_t size /* = 0 */, + Message* outMsg /* = NULL */) { Message *msg; if (outMsg) @@ -219,7 +225,8 @@ bool Protocol::SendInMessage(int signal, CPayloadWrapBase *payload, Message *out return true; } -bool Protocol::SendOutMessageSync(int signal, Message **retMsg, int timeout, void *data /* = NULL */, size_t size /* = 0 */) +bool Protocol::SendOutMessageSync( + int signal, Message** retMsg, int timeout, const void* data /* = NULL */, size_t size /* = 0 */) { Message *msg = GetMessage(); msg->isOut = true; diff --git a/xbmc/utils/ActorProtocol.h b/xbmc/utils/ActorProtocol.h index 66d413e6cd629..97297a6ddee47 100644 --- a/xbmc/utils/ActorProtocol.h +++ b/xbmc/utils/ActorProtocol.h @@ -78,11 +78,18 @@ class Protocol ~Protocol(); Message *GetMessage(); void ReturnMessage(Message *msg); - bool SendOutMessage(int signal, void *data = nullptr, size_t size = 0, Message *outMsg = nullptr); + bool SendOutMessage(int signal, + const void* data = nullptr, + size_t size = 0, + Message* outMsg = nullptr); bool SendOutMessage(int signal, CPayloadWrapBase *payload, Message *outMsg = nullptr); - bool SendInMessage(int signal, void *data = nullptr, size_t size = 0, Message *outMsg = nullptr); + bool SendInMessage(int signal, + const void* data = nullptr, + size_t size = 0, + Message* outMsg = nullptr); bool SendInMessage(int signal, CPayloadWrapBase *payload, Message *outMsg = nullptr); - bool SendOutMessageSync(int signal, Message **retMsg, int timeout, void *data = nullptr, size_t size = 0); + bool SendOutMessageSync( + int signal, Message** retMsg, int timeout, const void* data = nullptr, size_t size = 0); bool SendOutMessageSync(int signal, Message **retMsg, int timeout, CPayloadWrapBase *payload); bool ReceiveOutMessage(Message **msg); bool ReceiveInMessage(Message **msg); From c1fb0f3b5c047af3e211acfffdecc931039d8a1b Mon Sep 17 00:00:00 2001 From: Rainer Hochecker Date: Sun, 10 Nov 2019 15:35:08 +0100 Subject: [PATCH 5/6] AESinkPULSE: Make CacheTotal dynamic --- xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 12 ++++++++++-- xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp index 8ff62bb83b543..6f694cc298ea6 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp @@ -722,6 +722,7 @@ CAESinkPULSE::CAESinkPULSE() m_BytesPerSecond = 0; m_BufferSize = 0; m_Channels = 0; + m_maxLatency = 0.0; m_Stream = NULL; m_Context = NULL; m_IsStreamPaused = false; @@ -746,6 +747,7 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) m_BytesPerSecond = 0; m_BufferSize = 0; m_Channels = 0; + m_maxLatency = 0.0; m_Stream = NULL; m_Context = NULL; m_periodSize = 0; @@ -989,6 +991,7 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) m_periodSize = a->minreq; format.m_frames = packetSize / frameSize; + m_maxLatency = static_cast(m_BufferSize) / m_BytesPerSecond; } { @@ -1029,6 +1032,7 @@ void CAESinkPULSE::Deinitialize() m_passthrough = false; m_periodSize = 0; m_requestedBytes = 0; + m_maxLatency = 0.0; if (m_Stream) Drain(); @@ -1076,13 +1080,17 @@ void CAESinkPULSE::GetDelay(AEDelayStatus& status) if (pa_stream_get_latency(m_Stream, &r_usec, &negative) < 0) r_usec = 0; + double delay = r_usec / 1000000.0; + if (delay > m_maxLatency) + m_maxLatency = delay; + pa_threaded_mainloop_unlock(m_MainLoop); - status.SetDelay(r_usec / 1000000.0); + status.SetDelay(delay); } double CAESinkPULSE::GetCacheTotal() { - return (float)m_BufferSize / (float)m_BytesPerSecond; + return m_maxLatency; } unsigned int CAESinkPULSE::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h index 0ece3b4910a9f..58b28b89e6e20 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h @@ -66,6 +66,7 @@ class CAESinkPULSE : public IAESink unsigned int m_BytesPerSecond; unsigned int m_BufferSize; unsigned int m_Channels; + double m_maxLatency; pa_stream *m_Stream; pa_cvolume m_Volume; From 4302b540c8d27d959a31b905b4d955d4b0e4dd83 Mon Sep 17 00:00:00 2001 From: Rainer Hochecker Date: Sun, 10 Nov 2019 16:03:43 +0100 Subject: [PATCH 6/6] ActiveAE: Align VizBuffer size with sink's max delay --- xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp index 9211b4f5f58da..a9e44b251aa8a 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp @@ -1381,12 +1381,12 @@ void CActiveAE::Configure(AEAudioFormat *desiredFmt) // input buffers m_vizBuffersInput = new CActiveAEBufferPool(m_internalFormat); - m_vizBuffersInput->Create(2000); + m_vizBuffersInput->Create(2000 + m_stats.GetMaxDelay() * 1000); // resample buffers m_vizBuffers = new CActiveAEBufferPoolResample(m_internalFormat, vizFormat, m_settings.resampleQuality); //! @todo use cache of sync + water level - m_vizBuffers->Create(2000, false, false); + m_vizBuffers->Create(2000 + m_stats.GetMaxDelay() * 1000, false, false); m_vizInitialized = false; } }