00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "audiopulseutil.h"
00023 #include "util.h"
00024 #include "exitcodes.h"
00025 #include "mythcontext.h"
00026
00027 #ifdef USING_PULSE
00028
00029 #include <sys/types.h>
00030 #include <sys/wait.h>
00031
00032 #include <signal.h>
00033 #include <string.h>
00034 #include <errno.h>
00035 #include <unistd.h>
00036 #include <assert.h>
00037 #include <stdio.h>
00038 #include <stdlib.h>
00039 #include <limits.h>
00040 #include <getopt.h>
00041 #include <locale.h>
00042
00043 #ifdef __linux__
00044 #include <sys/prctl.h>
00045 #endif
00046
00047 #include <pulse/pulseaudio.h>
00048
00049 #include <qthread.h>
00050 #include <qmutex.h>
00051 #include <qwaitcondition.h>
00052
00053 #define BUFSIZE 1024
00054
00055 #define LOC QString("AudioPulseUtil: ")
00056 #define LOC_WARN QString("AudioPulseUtil, Warning: ")
00057 #define LOC_ERR QString("AudioPulseUtil, Error: ")
00058
00059 enum pa_values {
00060 kPA_undefined = -1,
00061 kPA_suspended = +0,
00062 kPA_not_suspended_remote_server = +1,
00063 kPA_not_suspended_error = +2,
00064 kPA_not_suspended_success = +3,
00065 kPA_unsuspended_error = +4,
00066 kPA_unsuspended_success = +5,
00067 };
00068
00069 static pa_context *pau_context = NULL;
00070 static pa_mainloop_api *pau_mainloop_api = NULL;
00071 static QMutex pau_lock;
00072 static QWaitCondition pau_wait;
00073 static int pau_value = kPA_undefined;
00074
00075 static void pau_set_value(int new_value)
00076 {
00077 QMutexLocker ml(&pau_lock);
00078 pau_value = new_value;
00079 pau_wait.wakeAll();
00080 }
00081
00082 static void pau_quit(int ret)
00083 {
00084 if (pau_mainloop_api)
00085 pau_mainloop_api->quit(pau_mainloop_api, ret);
00086 }
00087
00088 static void pau_context_drain_complete(pa_context *c, void *userdata)
00089 {
00090 if (c)
00091 pa_context_disconnect(c);
00092 }
00093
00094 static void pau_drain(void)
00095 {
00096 if (!pau_context)
00097 return;
00098
00099 pa_operation *operation = pa_context_drain(pau_context, pau_context_drain_complete, NULL);
00100 if (!operation)
00101 pa_context_disconnect(pau_context);
00102 else
00103 pa_operation_unref(operation);
00104 }
00105
00106 static void pau_suspend_complete(pa_context *c, int success, void *userdata)
00107 {
00108 if (!success)
00109 {
00110 if (!c)
00111 return;
00112
00113 VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Failure to suspend: %1")
00114 .arg(pa_strerror(pa_context_errno(c))));
00115
00116 pau_set_value(kPA_not_suspended_error);
00117
00118 return;
00119 }
00120
00121 {
00122 QMutexLocker ml(&pau_lock);
00123 if (kPA_suspended == pau_value)
00124 return;
00125 }
00126
00127 VERBOSE(VB_GENERAL, LOC + "Suspend Success");
00128
00129 pau_set_value(kPA_suspended);
00130 }
00131
00132 static void pau_resume_complete(pa_context *c, int success, void *userdata)
00133 {
00134 static int n = 0;
00135
00136 n++;
00137
00138 if (!success)
00139 {
00140 if (!c)
00141 return;
00142
00143 VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Failure to resume: %1")
00144 .arg(pa_strerror(pa_context_errno(c))));
00145
00146 pau_set_value(kPA_unsuspended_error);
00147
00148 return;
00149 }
00150
00151 if (n >= 2)
00152 pau_drain();
00153
00154 {
00155 QMutexLocker ml(&pau_lock);
00156 if (kPA_unsuspended_success == pau_value)
00157 return;
00158 }
00159
00160 VERBOSE(VB_GENERAL, LOC + "Resume Success");
00161
00162 pau_set_value(kPA_unsuspended_success);
00163 }
00164
00165 static void pau_context_state_callback(pa_context *c, void *userdata)
00166 {
00167 if (!c)
00168 return;
00169
00170 switch (pa_context_get_state(c))
00171 {
00172 case PA_CONTEXT_CONNECTING:
00173 case PA_CONTEXT_AUTHORIZING:
00174 case PA_CONTEXT_SETTING_NAME:
00175 break;
00176
00177 case PA_CONTEXT_READY:
00178 if (pa_context_is_local(c))
00179 {
00180 pa_operation *operation_sink =
00181 pa_context_suspend_sink_by_index(
00182 c, PA_INVALID_INDEX, 1, pau_suspend_complete, NULL);
00183 pa_operation_unref(operation_sink);
00184
00185 pa_operation *operation_source =
00186 pa_context_suspend_source_by_index(
00187 c, PA_INVALID_INDEX, 1, pau_suspend_complete, NULL);
00188 pa_operation_unref(operation_source);
00189 }
00190 else
00191 {
00192 VERBOSE(VB_IMPORTANT, LOC_ERR +
00193 "Sound server is not local, can not suspend.");
00194
00195 pau_set_value(kPA_not_suspended_remote_server);
00196 }
00197
00198 break;
00199
00200 case PA_CONTEXT_TERMINATED:
00201 pau_quit(0);
00202 break;
00203
00204 case PA_CONTEXT_FAILED:
00205 default:
00206 VERBOSE(VB_IMPORTANT, LOC_WARN +
00207 "Can not connect to sound server, can not suspend." +
00208 QString("\n\t\t\t%1")
00209 .arg(pa_strerror(pa_context_errno(c))));
00210
00211 pau_set_value(kPA_not_suspended_error);
00212
00213 if (pau_context)
00214 {
00215 pa_context_unref(pau_context);
00216 pau_context = NULL;
00217 }
00218
00219 break;
00220 }
00221 }
00222
00223 void pau_pulseaudio_suspend_internal(void)
00224 {
00225 pa_mainloop* m = NULL;
00226 int ret = 1;
00227 char *server = NULL;
00228 const char *bn = "mythtv";
00229
00230 if (!(m = pa_mainloop_new()))
00231 {
00232 VERBOSE(VB_IMPORTANT, LOC_ERR + "pa_mainloop_new() failed.");
00233 goto quit;
00234 }
00235
00236 pau_mainloop_api = pa_mainloop_get_api(m);
00237 if (!pau_mainloop_api)
00238 goto quit;
00239
00240 if (pa_signal_init(pau_mainloop_api) != 0)
00241 goto quit;
00242
00243 if (!(pau_context = pa_context_new(pau_mainloop_api, bn)))
00244 {
00245 VERBOSE(VB_IMPORTANT, LOC_ERR + "pa_context_new() failed.");
00246 goto quit;
00247 }
00248
00249 pa_context_set_state_callback(pau_context, pau_context_state_callback, NULL);
00250 pa_context_connect(pau_context, server, PA_CONTEXT_NOAUTOSPAWN, NULL);
00251
00252 if (pa_mainloop_run(m, &ret) < 0)
00253 {
00254 VERBOSE(VB_IMPORTANT, LOC_ERR + "pa_mainloop_run() failed.\n");
00255 goto quit;
00256 }
00257
00258 quit:
00259 if (pau_context)
00260 pa_context_unref(pau_context);
00261
00262 if (m) {
00263 pa_signal_done();
00264 pa_mainloop_free(m);
00265 }
00266
00267 pa_xfree(server);
00268 }
00269
00270 class PAThread : public QThread
00271 {
00272 public:
00273 void run(void)
00274 {
00275 pau_pulseaudio_suspend_internal();
00276 }
00277 };
00278
00280 bool pulseaudio_suspend(void)
00281 {
00282 QThread *t = new PAThread();
00283 t->start();
00284
00285 QMutexLocker ml(&pau_lock);
00286 while (kPA_undefined == pau_value)
00287 pau_wait.wait(&pau_lock);
00288
00289 return kPA_suspended == pau_value;
00290 }
00291
00293 bool pulseaudio_unsuspend(void)
00294 {
00295 if (!pau_context)
00296 {
00297 pau_quit(0);
00298 return false;
00299 }
00300
00301 if (pa_context_is_local(pau_context))
00302 {
00303 pa_operation *operation_sink =
00304 pa_context_suspend_sink_by_index(
00305 pau_context, PA_INVALID_INDEX, 0, pau_resume_complete, NULL);
00306 pa_operation_unref(operation_sink);
00307
00308 pa_operation *operation_source =
00309 pa_context_suspend_source_by_index(
00310 pau_context, PA_INVALID_INDEX, 0, pau_resume_complete, NULL);
00311 pa_operation_unref(operation_source);
00312
00313 QMutexLocker ml(&pau_lock);
00314 while ((kPA_unsuspended_error != pau_value) &&
00315 (kPA_unsuspended_success != pau_value))
00316 {
00317 pau_wait.wait(&pau_lock);
00318 }
00319
00320 return kPA_unsuspended_success == pau_value;
00321 }
00322 else
00323 {
00324 pau_drain();
00325 }
00326
00327 return false;
00328 }
00329
00330 #endif // USING_PULSE
00331
00332 int pulseaudio_handle_startup(void)
00333 {
00334 #ifdef USING_PULSE
00335 if (getenv("EXPERIMENTALLY_ALLOW_PULSE_AUDIO"))
00336 {
00337 VERBOSE(VB_IMPORTANT, "WARNING: ");
00338 VERBOSE(VB_IMPORTANT, "WARNING: ***Pulse Audio is running!!!!***");
00339 VERBOSE(VB_IMPORTANT, "WARNING: ");
00340 VERBOSE(VB_IMPORTANT, "WARNING: You have told MythTV to ignore it.");
00341 VERBOSE(VB_IMPORTANT, "WARNING: ");
00342 }
00343 else if (IsPulseAudioRunning() && !pulseaudio_suspend())
00344 {
00345 VERBOSE(VB_IMPORTANT, "ERROR: ***Pulse Audio is running!!!!***");
00346 VERBOSE(VB_IMPORTANT,
00347 "ERROR: But MythTV was not able to suspend it. EXITING!");
00348
00349 return GENERIC_EXIT_NOT_OK;
00350 }
00351 #else
00352 if (IsPulseAudioRunning())
00353 {
00354 VERBOSE(VB_IMPORTANT, "ERROR: ***Pulse Audio is running!!!!***");
00355 VERBOSE(VB_IMPORTANT, "ERROR: But MythTV has not been compiled "
00356 "with Pulse Audio disabling support. EXITING!");
00357 return GENERIC_EXIT_NOT_OK;
00358 }
00359 #endif
00360
00361 return GENERIC_EXIT_OK;
00362 }
00363
00364 int pulseaudio_handle_teardown(void)
00365 {
00366 #ifdef USING_PULSE
00367 if (getenv("EXPERIMENTALLY_ALLOW_PULSE_AUDIO"))
00368 return GENERIC_EXIT_OK;
00369
00370 {
00371 QMutexLocker ml(&pau_lock);
00372 if (kPA_suspended != pau_value)
00373 return GENERIC_EXIT_OK;
00374 }
00375
00376 if (!pulseaudio_unsuspend())
00377 {
00378 VERBOSE(VB_IMPORTANT, "ERROR: Encountered error re-enabling pulse audio");
00379 }
00380 #endif
00381
00382 return GENERIC_EXIT_OK;
00383 }