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