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