MythTV
master
libs
libmyth
audio
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
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
35
PulseHandler
*
PulseHandler::g_pulseHandler
=
nullptr
;
36
bool
PulseHandler::g_pulseHandlerActive
=
false
;
37
38
bool
PulseHandler::Suspend
(
enum
PulseAction
action
)
39
{
40
// global lock around all access to our global singleton
41
static
QMutex s_globalLock;
42
QMutexLocker locker(&s_globalLock);
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
QElapsedTimer s_time;
58
static
auto
s_ePulseAction =
PulseAction
(-1);
59
60
// Use the last result of IsPulseAudioRunning if within time
61
if
(s_time.isValid() && !s_time.hasExpired(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
86
if
(
g_pulseHandler
&& !
g_pulseHandler
->
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
auto
* 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
// enable processing of incoming callbacks
111
g_pulseHandlerActive
=
true
;
112
bool
result =
g_pulseHandler
->
SuspendInternal
(
kPulseSuspend
==
action
);
113
// disable processing of incoming callbacks in case we delete/recreate our
114
// instance due to a termination or other failure
115
g_pulseHandlerActive
=
false
;
116
s_ePulseAction =
action
;
117
return
result;
118
}
119
120
static
void
StatusCallback
(pa_context *ctx,
void
*userdata)
121
{
122
// ignore any status updates while we're inactive, we can update
123
// directly as needed
124
if
(!ctx || !
PulseHandler::g_pulseHandlerActive
)
125
return
;
126
127
// validate the callback
128
auto
*handler =
static_cast<
PulseHandler
*
>
(userdata);
129
if
(!handler)
130
{
131
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
"Callback: no handler."
);
132
return
;
133
}
134
135
if
(handler->m_ctx != ctx)
136
{
137
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
"Callback: handler/context mismatch."
);
138
return
;
139
}
140
141
if
(handler !=
PulseHandler::g_pulseHandler
)
142
{
143
LOG
(VB_GENERAL, LOG_ERR,
144
"Callback: returned handler is not the global handler."
);
145
return
;
146
}
147
148
// update our status
149
pa_context_state state = pa_context_get_state(ctx);
150
LOG
(VB_AUDIO, LOG_INFO,
LOC
+ QString(
"Callback: State changed %1->%2"
)
151
.
arg
(
state_to_string
(handler->m_ctxState))
152
.arg(
state_to_string
(state)));
153
handler->m_ctxState = state;
154
}
155
156
static
void
OperationCallback
(pa_context *ctx,
int
success,
void
*userdata)
157
{
158
if
(!ctx)
159
return
;
160
161
// ignore late updates but flag them as they may be an issue
162
if
(!
PulseHandler::g_pulseHandlerActive
)
163
{
164
LOG
(VB_GENERAL, LOG_WARNING,
LOC
+
165
"Received a late/unexpected operation callback. Ignoring."
);
166
return
;
167
}
168
169
// validate the callback
170
auto
*handler =
static_cast<
PulseHandler
*
>
(userdata);
171
if
(!handler)
172
{
173
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
"Operation: no handler."
);
174
return
;
175
}
176
177
if
(handler->m_ctx != ctx)
178
{
179
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
"Operation: handler/context mismatch."
);
180
return
;
181
}
182
183
if
(handler !=
PulseHandler::g_pulseHandler
)
184
{
185
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
186
"Operation: returned handler is not the global handler."
);
187
return
;
188
}
189
190
// update the context
191
handler->m_pendingOperations--;
192
LOG
(VB_AUDIO, LOG_INFO,
LOC
+ QString(
"Operation: success %1 remaining %2"
)
193
.
arg
(success).
arg
(handler->m_pendingOperations));
194
}
195
196
PulseHandler::~PulseHandler
(
void
)
197
{
198
// TODO - do we need to drain the context??
199
200
LOG
(VB_AUDIO, LOG_INFO,
LOC
+
"Destroying PulseAudio handler"
);
201
202
// is this correct?
203
if
(
m_ctx
)
204
{
205
pa_context_disconnect(
m_ctx
);
206
pa_context_unref(
m_ctx
);
207
}
208
209
if
(
m_loop
)
210
{
211
pa_signal_done();
212
pa_mainloop_free(
m_loop
);
213
}
214
}
215
216
bool
PulseHandler::Valid
(
void
)
217
{
218
if
(
m_initialised
&&
m_valid
)
219
{
220
m_ctxState
= pa_context_get_state(
m_ctx
);
221
return
PA_CONTEXT_READY ==
m_ctxState
;
222
}
223
return
false
;
224
}
225
226
bool
PulseHandler::Init
(
void
)
227
{
228
if
(
m_initialised
)
229
return
m_valid
;
230
m_initialised
=
true
;
231
232
// Initialse our connection to the server
233
m_loop
= pa_mainloop_new();
234
if
(!
m_loop
)
235
{
236
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
"Failed to get PulseAudio mainloop"
);
237
return
m_valid
;
238
}
239
240
pa_mainloop_api *api = pa_mainloop_get_api(
m_loop
);
241
if
(!api)
242
{
243
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
"Failed to get PulseAudio api"
);
244
return
m_valid
;
245
}
246
247
if
(pa_signal_init(api) != 0)
248
{
249
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
"Failed to initialise signaling"
);
250
return
m_valid
;
251
}
252
253
const
char
*client =
"mythtv"
;
254
m_ctx
= pa_context_new(api, client);
255
if
(!
m_ctx
)
256
{
257
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
"Failed to create context"
);
258
return
m_valid
;
259
}
260
261
// remember which thread created this object for later sanity debugging
262
m_thread
= QThread::currentThread();
263
264
// we set the callback, connect and then run the main loop 'by hand'
265
// until we've successfully connected (or not)
266
pa_context_set_state_callback(
m_ctx
,
StatusCallback
,
this
);
267
pa_context_connect(
m_ctx
,
nullptr
, PA_CONTEXT_NOAUTOSPAWN,
nullptr
);
268
int
ret = 0;
269
int
tries = 0;
270
while
((tries++ < 100) && !
IS_READY
(
m_ctxState
))
271
{
272
pa_mainloop_iterate(
m_loop
, 0, &ret);
273
usleep(10000);
274
}
275
276
if
(PA_CONTEXT_READY !=
m_ctxState
)
277
{
278
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
"Context not ready after 1000ms"
);
279
return
m_valid
;
280
}
281
282
LOG
(VB_AUDIO, LOG_INFO,
LOC
+
"Initialised handler"
);
283
m_valid
=
true
;
284
return
m_valid
;
285
}
286
287
bool
PulseHandler::SuspendInternal
(
bool
suspend)
288
{
289
// set everything up...
290
if
(!
Init
())
291
return
false
;
292
293
// just in case it all goes pete tong
294
if
(!
is_current_thread
(
m_thread
))
295
LOG
(VB_AUDIO, LOG_WARNING,
LOC
+
296
"PulseHandler called from a different thread"
);
297
298
QString
action
= suspend ?
"suspend"
:
"resume"
;
299
// don't bother to suspend a networked server
300
if
(!pa_context_is_local(
m_ctx
))
301
{
302
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
303
"PulseAudio server is remote. No need to "
+
action
);
304
return
false
;
305
}
306
307
// create and dispatch 2 operations to suspend or resume all current sinks
308
// and all current sources
309
m_pendingOperations
= 2;
310
pa_operation *operation_sink =
311
pa_context_suspend_sink_by_index(
312
m_ctx
, PA_INVALID_INDEX,
static_cast<
int
>
(suspend),
OperationCallback
,
this
);
313
pa_operation_unref(operation_sink);
314
315
pa_operation *operation_source =
316
pa_context_suspend_source_by_index(
317
m_ctx
, PA_INVALID_INDEX,
static_cast<
int
>
(suspend),
OperationCallback
,
this
);
318
pa_operation_unref(operation_source);
319
320
// run the loop manually and wait for the callbacks
321
int
count = 0;
322
int
ret = 0;
323
while
(
m_pendingOperations
&& count++ < 100)
324
{
325
pa_mainloop_iterate(
m_loop
, 0, &ret);
326
usleep(10000);
327
}
328
329
// a failure isn't necessarily disastrous
330
if
(
m_pendingOperations
)
331
{
332
m_pendingOperations
= 0;
333
LOG
(VB_GENERAL, LOG_ERR,
LOC
+
"Failed to "
+
action
);
334
return
false
;
335
}
336
337
// rejoice
338
LOG
(VB_GENERAL, LOG_INFO,
LOC
+
"PulseAudio "
+
action
+
" OK"
);
339
return
true
;
340
}
StatusCallback
void(*)(int, void *) StatusCallback
Definition:
mythplayer.h:49
PulseHandler::Suspend
static bool Suspend(enum PulseAction action)
Definition:
audiopulsehandler.cpp:38
PulseHandler::m_ctx
pa_context * m_ctx
Definition:
audiopulsehandler.h:26
PulseHandler::Valid
bool Valid(void)
Definition:
audiopulsehandler.cpp:216
PulseHandler::m_loop
pa_mainloop * m_loop
Definition:
audiopulsehandler.h:34
arg
arg(title).arg(filename).arg(doDelete))
PulseHandler::PulseAction
PulseAction
Definition:
audiopulsehandler.h:11
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition:
mythlogging.h:23
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:19
IS_READY
#define IS_READY(arg)
Definition:
audiopulsehandler.cpp:15
StatusCallback
static void StatusCallback(pa_context *ctx, void *userdata)
Definition:
audiopulsehandler.cpp:120
IsPulseAudioRunning
bool IsPulseAudioRunning(void)
Is A/V Sync destruction daemon is running on this host?
Definition:
mythmiscutil.cpp:684
PulseHandler::m_thread
QThread * m_thread
Definition:
audiopulsehandler.h:37
mythlogging.h
PulseHandler::Init
bool Init(void)
Definition:
audiopulsehandler.cpp:226
LOC
#define LOC
Definition:
audiopulsehandler.cpp:13
PulseHandler::~PulseHandler
~PulseHandler(void)
Definition:
audiopulsehandler.cpp:196
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::kPulseSuspend
@ kPulseSuspend
Definition:
audiopulsehandler.h:13
PulseHandler::SuspendInternal
bool SuspendInternal(bool suspend)
Definition:
audiopulsehandler.cpp:287
PulseHandler::g_pulseHandler
static PulseHandler * g_pulseHandler
Definition:
audiopulsehandler.h:19
mythmiscutil.h
PulseHandler::m_initialised
bool m_initialised
Definition:
audiopulsehandler.h:35
mthread.h
build_compdb.action
action
Definition:
build_compdb.py:9
PulseHandler::kPulseCleanup
@ kPulseCleanup
Definition:
audiopulsehandler.h:15
PulseHandler::g_pulseHandlerActive
static bool g_pulseHandlerActive
Definition:
audiopulsehandler.h:20
OperationCallback
static void OperationCallback(pa_context *ctx, int success, void *userdata)
Definition:
audiopulsehandler.cpp:156
Generated on Mon Jan 18 2021 03:16:48 for MythTV by
1.8.17