00001 #include <QMutexLocker>
00002 #include <QString>
00003 #include <QMutex>
00004
00005 #include "audiopulsehandler.h"
00006 #include "mythlogging.h"
00007 #include "mthread.h"
00008 #include "mythmiscutil.h"
00009
00010 #define LOC QString("Pulse: ")
00011
00012 #define IS_READY(arg) ((PA_CONTEXT_READY == arg) || \
00013 (PA_CONTEXT_FAILED == arg) || \
00014 (PA_CONTEXT_TERMINATED == arg))
00015
00016 static QString state_to_string(pa_context_state state)
00017 {
00018 QString ret = "Unknown";
00019 switch (state)
00020 {
00021 case PA_CONTEXT_UNCONNECTED: ret = "Unconnected"; break;
00022 case PA_CONTEXT_CONNECTING: ret = "Connecting"; break;
00023 case PA_CONTEXT_AUTHORIZING: ret = "Authorizing"; break;
00024 case PA_CONTEXT_SETTING_NAME: ret = "Setting Name"; break;
00025 case PA_CONTEXT_READY: ret = "Ready!"; break;
00026 case PA_CONTEXT_FAILED: ret = "Failed"; break;
00027 case PA_CONTEXT_TERMINATED: ret = "Terminated"; break;
00028 }
00029 return ret;
00030 }
00031
00032 PulseHandler* PulseHandler::g_pulseHandler = NULL;
00033 bool PulseHandler::g_pulseHandlerActive = false;
00034
00035 bool PulseHandler::Suspend(enum PulseAction action)
00036 {
00037
00038 static QMutex global_lock;
00039 QMutexLocker locker(&global_lock);
00040
00041
00042 if (kPulseCleanup == action)
00043 {
00044 if (g_pulseHandler)
00045 {
00046 LOG(VB_GENERAL, LOG_INFO, LOC + "Cleaning up PulseHandler");
00047 delete g_pulseHandler;
00048 g_pulseHandler = NULL;
00049 }
00050 return true;
00051 }
00052
00053
00054 if (!IsPulseAudioRunning())
00055 {
00056 LOG(VB_AUDIO, LOG_INFO, LOC + "PulseAudio not running");
00057 return false;
00058 }
00059
00060
00061 if (g_pulseHandler && !g_pulseHandler->Valid())
00062 {
00063 LOG(VB_AUDIO, LOG_INFO, LOC + "PulseHandler invalidated. Deleting.");
00064 delete g_pulseHandler;
00065 g_pulseHandler = NULL;
00066 }
00067
00068
00069 if (!g_pulseHandler)
00070 {
00071 PulseHandler* handler = new PulseHandler();
00072 if (handler)
00073 {
00074 LOG(VB_AUDIO, LOG_INFO, LOC + "Created PulseHandler object");
00075 g_pulseHandler = handler;
00076 }
00077 else
00078 {
00079 LOG(VB_GENERAL, LOG_ERR, LOC +
00080 "Failed to create PulseHandler object");
00081 return false;
00082 }
00083 }
00084
00085 bool result;
00086
00087 g_pulseHandlerActive = true;
00088 result = g_pulseHandler->SuspendInternal(kPulseSuspend == action);
00089
00090
00091 g_pulseHandlerActive = false;
00092 return result;
00093 }
00094
00095 static void StatusCallback(pa_context *ctx, void *userdata)
00096 {
00097
00098
00099 if (!ctx || !PulseHandler::g_pulseHandlerActive)
00100 return;
00101
00102
00103 PulseHandler *handler = static_cast<PulseHandler*>(userdata);
00104 if (!handler)
00105 {
00106 LOG(VB_GENERAL, LOG_ERR, LOC + "Callback: no handler.");
00107 return;
00108 }
00109
00110 if (handler->m_ctx != ctx)
00111 {
00112 LOG(VB_GENERAL, LOG_ERR, LOC + "Callback: handler/context mismatch.");
00113 return;
00114 }
00115
00116 if (handler != PulseHandler::g_pulseHandler)
00117 {
00118 LOG(VB_GENERAL, LOG_ERR,
00119 "Callback: returned handler is not the global handler.");
00120 return;
00121 }
00122
00123
00124 pa_context_state state = pa_context_get_state(ctx);
00125 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Callback: State changed %1->%2")
00126 .arg(state_to_string(handler->m_ctx_state))
00127 .arg(state_to_string(state)));
00128 handler->m_ctx_state = state;
00129 }
00130
00131 static void OperationCallback(pa_context *ctx, int success, void *userdata)
00132 {
00133 if (!ctx)
00134 return;
00135
00136
00137 if (!PulseHandler::g_pulseHandlerActive)
00138 {
00139 LOG(VB_GENERAL, LOG_WARNING, LOC +
00140 "Received a late/unexpected operation callback. Ignoring.");
00141 return;
00142 }
00143
00144
00145 PulseHandler *handler = static_cast<PulseHandler*>(userdata);
00146 if (!handler)
00147 {
00148 LOG(VB_GENERAL, LOG_ERR, LOC + "Operation: no handler.");
00149 return;
00150 }
00151
00152 if (handler->m_ctx != ctx)
00153 {
00154 LOG(VB_GENERAL, LOG_ERR, LOC + "Operation: handler/context mismatch.");
00155 return;
00156 }
00157
00158 if (handler != PulseHandler::g_pulseHandler)
00159 {
00160 LOG(VB_GENERAL, LOG_ERR, LOC +
00161 "Operation: returned handler is not the global handler.");
00162 return;
00163 }
00164
00165
00166 handler->m_pending_operations--;
00167 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Operation: success %1 remaining %2")
00168 .arg(success).arg(handler->m_pending_operations));
00169 }
00170
00171 PulseHandler::PulseHandler(void)
00172 : m_ctx_state(PA_CONTEXT_UNCONNECTED), m_ctx(NULL), m_pending_operations(0),
00173 m_loop(NULL), m_initialised(false), m_valid(false),
00174 m_thread(NULL)
00175 {
00176 }
00177
00178 PulseHandler::~PulseHandler(void)
00179 {
00180
00181
00182 LOG(VB_AUDIO, LOG_INFO, LOC + "Destroying PulseAudio handler");
00183
00184
00185 if (m_ctx)
00186 {
00187 pa_context_disconnect(m_ctx);
00188 pa_context_unref(m_ctx);
00189 }
00190
00191 if (m_loop)
00192 {
00193 pa_signal_done();
00194 pa_mainloop_free(m_loop);
00195 }
00196 }
00197
00198 bool PulseHandler::Valid(void)
00199 {
00200 if (m_initialised && m_valid)
00201 {
00202 m_ctx_state = pa_context_get_state(m_ctx);
00203 return PA_CONTEXT_READY == m_ctx_state;
00204 }
00205 return false;
00206 }
00207
00208 bool PulseHandler::Init(void)
00209 {
00210 if (m_initialised)
00211 return m_valid;
00212 m_initialised = true;
00213
00214
00215 m_loop = pa_mainloop_new();
00216 if (!m_loop)
00217 {
00218 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to get PulseAudio mainloop");
00219 return m_valid;
00220 }
00221
00222 pa_mainloop_api *api = pa_mainloop_get_api(m_loop);
00223 if (!api)
00224 {
00225 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to get PulseAudio api");
00226 return m_valid;
00227 }
00228
00229 if (pa_signal_init(api) != 0)
00230 {
00231 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to initialise signaling");
00232 return m_valid;
00233 }
00234
00235 const char *client = "mythtv";
00236 m_ctx = pa_context_new(api, client);
00237 if (!m_ctx)
00238 {
00239 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create context");
00240 return m_valid;
00241 }
00242
00243
00244 m_thread = QThread::currentThread();
00245
00246
00247
00248 pa_context_set_state_callback(m_ctx, StatusCallback, this);
00249 pa_context_connect(m_ctx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
00250 int ret = 0;
00251 int tries = 0;
00252 while ((tries++ < 100) && !IS_READY(m_ctx_state))
00253 {
00254 pa_mainloop_iterate(m_loop, 0, &ret);
00255 usleep(10000);
00256 }
00257
00258 if (PA_CONTEXT_READY != m_ctx_state)
00259 {
00260 LOG(VB_GENERAL, LOG_ERR, LOC + "Context not ready after 1000ms");
00261 return m_valid;
00262 }
00263
00264 LOG(VB_AUDIO, LOG_INFO, LOC + "Initialised handler");
00265 m_valid = true;
00266 return m_valid;
00267 }
00268
00269 bool PulseHandler::SuspendInternal(bool suspend)
00270 {
00271
00272 if (!Init())
00273 return false;
00274
00275
00276 if (!is_current_thread(m_thread))
00277 LOG(VB_AUDIO, LOG_WARNING, LOC +
00278 "PulseHandler called from a different thread");
00279
00280 QString action = suspend ? "suspend" : "resume";
00281
00282 if (!pa_context_is_local(m_ctx))
00283 {
00284 LOG(VB_GENERAL, LOG_ERR, LOC +
00285 "PulseAudio server is remote. No need to " + action);
00286 return false;
00287 }
00288
00289
00290
00291 m_pending_operations = 2;
00292 pa_operation *operation_sink =
00293 pa_context_suspend_sink_by_index(
00294 m_ctx, PA_INVALID_INDEX, suspend, OperationCallback, this);
00295 pa_operation_unref(operation_sink);
00296
00297 pa_operation *operation_source =
00298 pa_context_suspend_source_by_index(
00299 m_ctx, PA_INVALID_INDEX, suspend, OperationCallback, this);
00300 pa_operation_unref(operation_source);
00301
00302
00303 int count = 0;
00304 int ret = 0;
00305 while (m_pending_operations && count++ < 100)
00306 {
00307 pa_mainloop_iterate(m_loop, 0, &ret);
00308 usleep(10000);
00309 }
00310
00311
00312 if (m_pending_operations)
00313 {
00314 m_pending_operations = 0;
00315 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to " + action);
00316 return false;
00317 }
00318
00319
00320 LOG(VB_GENERAL, LOG_INFO, LOC + "PulseAudio " + action + " OK");
00321 return true;
00322 }