MythTV  master
audiopulsehandler.cpp
Go to the documentation of this file.
1 #include <QMutexLocker>
2 #include <QString>
3 #include <QMutex>
4 #include <QElapsedTimer>
5 
6 #include <unistd.h> // for usleep
7 
10 #include "libmythbase/mthread.h"
11 
12 #include "audiopulsehandler.h"
13 
14 #define LOC QString("Pulse: ")
15 
16 static inline bool IS_READY(pa_context_state arg)
17 {
18  return ((PA_CONTEXT_READY == arg) ||
19  (PA_CONTEXT_FAILED == arg) ||
20  (PA_CONTEXT_TERMINATED == arg));
21 }
22 
23 static QString state_to_string(pa_context_state state)
24 {
25  QString ret = "Unknown";
26  switch (state)
27  {
28  case PA_CONTEXT_UNCONNECTED: ret = "Unconnected"; break;
29  case PA_CONTEXT_CONNECTING: ret = "Connecting"; break;
30  case PA_CONTEXT_AUTHORIZING: ret = "Authorizing"; break;
31  case PA_CONTEXT_SETTING_NAME: ret = "Setting Name"; break;
32  case PA_CONTEXT_READY: ret = "Ready!"; break;
33  case PA_CONTEXT_FAILED: ret = "Failed"; break;
34  case PA_CONTEXT_TERMINATED: ret = "Terminated"; break;
35  }
36  return ret;
37 }
38 
41 
43 {
44  // global lock around all access to our global singleton
45  static QMutex s_globalLock;
46  QMutexLocker locker(&s_globalLock);
47 
48  // cleanup the PulseAudio server connection if requested
49  if (kPulseCleanup == action)
50  {
51  if (g_pulseHandler)
52  {
53  LOG(VB_GENERAL, LOG_INFO, LOC + "Cleaning up PulseHandler");
54  delete g_pulseHandler;
55  g_pulseHandler = nullptr;
56  }
57  return true;
58  }
59 
60  static int s_iPulseRunning = -1;
61  static QElapsedTimer s_time;
62  static auto s_ePulseAction = PulseAction(-1);
63 
64  // Use the last result of IsPulseAudioRunning if within time
65  if (s_time.isValid() && !s_time.hasExpired(30000))
66  {
67  if (!s_iPulseRunning)
68  return false;
69 
70  // If the last action is repeated then do nothing
71  if (action == s_ePulseAction)
72  return true;
73  }
74  // NB IsPulseAudioRunning calls myth_system and can take up to 100mS
75  else if (IsPulseAudioRunning())
76  {
77  s_iPulseRunning = 1;
78  s_time.start();
79  }
80  else
81  {
82  // do nothing if PulseAudio is not currently running
83  LOG(VB_AUDIO, LOG_INFO, LOC + "PulseAudio not running");
84  s_iPulseRunning = 0;
85  s_time.start();
86  return false;
87  }
88 
89  // make sure any pre-existing handler is still valid
91  {
92  LOG(VB_AUDIO, LOG_INFO, LOC + "PulseHandler invalidated. Deleting.");
93  delete g_pulseHandler;
94  g_pulseHandler = nullptr;
95  }
96 
97  // create our handler
98  if (!g_pulseHandler)
99  {
100  auto* handler = new PulseHandler();
101  if (handler)
102  {
103  LOG(VB_AUDIO, LOG_INFO, LOC + "Created PulseHandler object");
104  g_pulseHandler = handler;
105  }
106  else
107  {
108  LOG(VB_GENERAL, LOG_ERR, LOC +
109  "Failed to create PulseHandler object");
110  return false;
111  }
112  }
113 
114  // enable processing of incoming callbacks
115  g_pulseHandlerActive = true;
117  // disable processing of incoming callbacks in case we delete/recreate our
118  // instance due to a termination or other failure
119  g_pulseHandlerActive = false;
120  s_ePulseAction = action;
121  return result;
122 }
123 
124 static void StatusCallback(pa_context *ctx, void *userdata)
125 {
126  // ignore any status updates while we're inactive, we can update
127  // directly as needed
129  return;
130 
131  // validate the callback
132  auto *handler = static_cast<PulseHandler*>(userdata);
133  if (!handler)
134  {
135  LOG(VB_GENERAL, LOG_ERR, LOC + "Callback: no handler.");
136  return;
137  }
138 
139  if (handler->m_ctx != ctx)
140  {
141  LOG(VB_GENERAL, LOG_ERR, LOC + "Callback: handler/context mismatch.");
142  return;
143  }
144 
145  if (handler != PulseHandler::g_pulseHandler)
146  {
147  LOG(VB_GENERAL, LOG_ERR,
148  "Callback: returned handler is not the global handler.");
149  return;
150  }
151 
152  // update our status
153  pa_context_state state = pa_context_get_state(ctx);
154  LOG(VB_AUDIO, LOG_INFO, LOC + QString("Callback: State changed %1->%2")
155  .arg(state_to_string(handler->m_ctxState), state_to_string(state)));
156  handler->m_ctxState = state;
157 }
158 
159 static void OperationCallback(pa_context *ctx, int success, void *userdata)
160 {
161  if (!ctx)
162  return;
163 
164  // ignore late updates but flag them as they may be an issue
166  {
167  LOG(VB_GENERAL, LOG_WARNING, LOC +
168  "Received a late/unexpected operation callback. Ignoring.");
169  return;
170  }
171 
172  // validate the callback
173  auto *handler = static_cast<PulseHandler*>(userdata);
174  if (!handler)
175  {
176  LOG(VB_GENERAL, LOG_ERR, LOC + "Operation: no handler.");
177  return;
178  }
179 
180  if (handler->m_ctx != ctx)
181  {
182  LOG(VB_GENERAL, LOG_ERR, LOC + "Operation: handler/context mismatch.");
183  return;
184  }
185 
186  if (handler != PulseHandler::g_pulseHandler)
187  {
188  LOG(VB_GENERAL, LOG_ERR, LOC +
189  "Operation: returned handler is not the global handler.");
190  return;
191  }
192 
193  // update the context
194  handler->m_pendingOperations--;
195  LOG(VB_AUDIO, LOG_INFO, LOC + QString("Operation: success %1 remaining %2")
196  .arg(success).arg(handler->m_pendingOperations));
197 }
198 
200 {
201  // TODO - do we need to drain the context??
202 
203  LOG(VB_AUDIO, LOG_INFO, LOC + "Destroying PulseAudio handler");
204 
205  // is this correct?
206  if (m_ctx)
207  {
208  pa_context_disconnect(m_ctx);
209  pa_context_unref(m_ctx);
210  }
211 
212  if (m_loop)
213  {
214  pa_signal_done();
215  pa_mainloop_free(m_loop);
216  }
217 }
218 
220 {
221  if (m_initialised && m_valid)
222  {
223  m_ctxState = pa_context_get_state(m_ctx);
224  return PA_CONTEXT_READY == m_ctxState;
225  }
226  return false;
227 }
228 
230 {
231  if (m_initialised)
232  return m_valid;
233  m_initialised = true;
234 
235  // Initialse our connection to the server
236  m_loop = pa_mainloop_new();
237  if (!m_loop)
238  {
239  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to get PulseAudio mainloop");
240  return m_valid;
241  }
242 
243  pa_mainloop_api *api = pa_mainloop_get_api(m_loop);
244  if (!api)
245  {
246  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to get PulseAudio api");
247  return m_valid;
248  }
249 
250  if (pa_signal_init(api) != 0)
251  {
252  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to initialise signaling");
253  return m_valid;
254  }
255 
256  const char *client = "mythtv";
257  m_ctx = pa_context_new(api, client);
258  if (!m_ctx)
259  {
260  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create context");
261  return m_valid;
262  }
263 
264  // remember which thread created this object for later sanity debugging
265  m_thread = QThread::currentThread();
266 
267  // we set the callback, connect and then run the main loop 'by hand'
268  // until we've successfully connected (or not)
269  pa_context_set_state_callback(m_ctx, StatusCallback, this);
270  pa_context_connect(m_ctx, nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr);
271  int ret = 0;
272  int tries = 0;
273  while ((tries++ < 100) && !IS_READY(m_ctxState))
274  {
275  pa_mainloop_iterate(m_loop, 0, &ret);
276  usleep(10000);
277  }
278 
279  if (PA_CONTEXT_READY != m_ctxState)
280  {
281  LOG(VB_GENERAL, LOG_ERR, LOC + "Context not ready after 1000ms");
282  return m_valid;
283  }
284 
285  LOG(VB_AUDIO, LOG_INFO, LOC + "Initialised handler");
286  m_valid = true;
287  return m_valid;
288 }
289 
291 {
292  // set everything up...
293  if (!Init())
294  return false;
295 
296  // just in case it all goes pete tong
298  LOG(VB_AUDIO, LOG_WARNING, LOC +
299  "PulseHandler called from a different thread");
300 
301  QString action = suspend ? "suspend" : "resume";
302  // don't bother to suspend a networked server
303  if (!pa_context_is_local(m_ctx))
304  {
305  LOG(VB_GENERAL, LOG_ERR, LOC +
306  "PulseAudio server is remote. No need to " + action);
307  return false;
308  }
309 
310  // create and dispatch 2 operations to suspend or resume all current sinks
311  // and all current sources
313  pa_operation *operation_sink =
314  pa_context_suspend_sink_by_index(
315  m_ctx, PA_INVALID_INDEX, static_cast<int>(suspend), OperationCallback, this);
316  pa_operation_unref(operation_sink);
317 
318  pa_operation *operation_source =
319  pa_context_suspend_source_by_index(
320  m_ctx, PA_INVALID_INDEX, static_cast<int>(suspend), OperationCallback, this);
321  pa_operation_unref(operation_source);
322 
323  // run the loop manually and wait for the callbacks
324  int count = 0;
325  int ret = 0;
326  while (m_pendingOperations && count++ < 100)
327  {
328  pa_mainloop_iterate(m_loop, 0, &ret);
329  usleep(10000);
330  }
331 
332  // a failure isn't necessarily disastrous
334  {
336  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to " + action);
337  return false;
338  }
339 
340  // rejoice
341  LOG(VB_GENERAL, LOG_INFO, LOC + "PulseAudio " + action + " OK");
342  return true;
343 }
StatusCallback
void(*)(int, void *) StatusCallback
Definition: mythplayer.h:51
PulseHandler::kPulseSuspend
@ kPulseSuspend
Definition: audiopulsehandler.h:13
PulseHandler::Suspend
static bool Suspend(enum PulseAction action)
Definition: audiopulsehandler.cpp:42
PulseHandler::m_ctx
pa_context * m_ctx
Definition: audiopulsehandler.h:26
PulseHandler::Valid
bool Valid(void)
Definition: audiopulsehandler.cpp:219
PulseHandler::m_loop
pa_mainloop * m_loop
Definition: audiopulsehandler.h:34
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
is_current_thread
bool is_current_thread(MThread *thread)
Use this to determine if you are in the named thread.
Definition: mthread.cpp:40
PulseHandler::m_pendingOperations
int m_pendingOperations
Definition: audiopulsehandler.h:27
state_to_string
static QString state_to_string(pa_context_state state)
Definition: audiopulsehandler.cpp:23
IS_READY
static bool IS_READY(pa_context_state arg)
Definition: audiopulsehandler.cpp:16
StatusCallback
static void StatusCallback(pa_context *ctx, void *userdata)
Definition: audiopulsehandler.cpp:124
IsPulseAudioRunning
bool IsPulseAudioRunning(void)
Is A/V Sync destruction daemon is running on this host?
Definition: mythmiscutil.cpp:638
PulseHandler::m_thread
QThread * m_thread
Definition: audiopulsehandler.h:37
mythlogging.h
PulseHandler::Init
bool Init(void)
Definition: audiopulsehandler.cpp:229
LOC
#define LOC
Definition: audiopulsehandler.cpp:14
PulseHandler::~PulseHandler
~PulseHandler(void)
Definition: audiopulsehandler.cpp:199
PulseHandler::m_ctxState
pa_context_state m_ctxState
Definition: audiopulsehandler.h:25
PulseHandler
Definition: audiopulsehandler.h:8
audiopulsehandler.h
PulseHandler::m_valid
bool m_valid
Definition: audiopulsehandler.h:36
PulseHandler::PulseHandler
PulseHandler(void)=default
PulseHandler::SuspendInternal
bool SuspendInternal(bool suspend)
Definition: audiopulsehandler.cpp:290
PulseHandler::g_pulseHandler
static PulseHandler * g_pulseHandler
Definition: audiopulsehandler.h:19
mythmiscutil.h
PulseHandler::m_initialised
bool m_initialised
Definition: audiopulsehandler.h:35
PulseHandler::PulseAction
PulseAction
Definition: audiopulsehandler.h:11
mthread.h
build_compdb.action
action
Definition: build_compdb.py:9
PulseHandler::g_pulseHandlerActive
static bool g_pulseHandlerActive
Definition: audiopulsehandler.h:20
PulseHandler::kPulseCleanup
@ kPulseCleanup
Definition: audiopulsehandler.h:15
OperationCallback
static void OperationCallback(pa_context *ctx, int success, void *userdata)
Definition: audiopulsehandler.cpp:159