2 * hfdialer - Hands Free Voice Call Manager
3 * Copyright (c) 2012, Intel Corporation.
5 * This program is licensed under the terms and conditions of the
6 * Apache License, version 2.0. The full text of the Apache License is at
7 * http://www.apache.org/licenses/LICENSE-2.0
11 #include "pacontrol.h"
12 #include "managerproxy.h"
13 #include "callmanager.h"
15 #include <QStringBuilder>
19 // Define our pulse audio loop and connection variables
20 static PAControl* paControl = new PAControl;
21 // Create a mainloop API and connection to the default server
22 static pa_glib_mainloop *pa_ml = NULL;
24 static void pa_subscribed_events_cb(pa_context *c, enum pa_subscription_event_type t, uint32_t , void *);
26 static void operation_callback(pa_context *c, int success, void *userdata) {
30 qDebug() << QString("Operation Failed");
31 paControl->setErrorMsg(QString("Operation Failed"));
35 static void module_callback(pa_context *c, uint32_t index, void *userdata) {
38 if (index == PA_INVALID_INDEX) {
39 qDebug() << QString("Load module failed");
40 paControl->setErrorMsg(QString("Load module failed"));
44 static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int is_last, void *userdata) {
55 //qDebug() << "pa_sourcelist_cb() Source added: " << l->name;
56 source = new PADevice();
57 source->index = l->index;
59 source->name = l->name;
60 if(l->description != NULL)
61 source->description = l->description;
62 paControl->addSource(source);
65 static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int is_last, void *userdata) {
76 sink = new PADevice();
77 sink->index = l->index;
80 if(l->description != NULL)
81 sink->description = l->description;
82 paControl->addSink(sink);
85 static void pa_modulelist_cb(pa_context *c, const pa_module_info *i, int is_last, void *userdata) {
96 module = new PAModule();
97 module->index = i->index;
99 module->name = i->name;
100 if(i->argument != NULL)
101 module->argument = i->argument;
102 paControl->addModule(module);
105 static void pa_state_cb(pa_context *c, void *) {
107 pa_context_state_t state = pa_context_get_state(c);
108 if(state == PA_CONTEXT_READY)
110 paControl->setState(true);
111 pa_context_set_subscribe_callback(c, pa_subscribed_events_cb, NULL);
113 if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
114 (PA_SUBSCRIPTION_MASK_MODULE|
115 PA_SUBSCRIPTION_MASK_SINK|
116 PA_SUBSCRIPTION_MASK_SOURCE|
117 PA_SUBSCRIPTION_MASK_SINK_INPUT|
118 PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) {
119 qWarning("pa_context_subscribe() failed");
121 if(o) pa_operation_unref(o);
122 ///Get an initial list of sinks, sources and modules.
124 pa_context_get_sink_info_list(c, pa_sinklist_cb, NULL);
126 pa_context_get_source_info_list(c, pa_sourcelist_cb, NULL);
128 pa_context_get_module_info_list(c, pa_modulelist_cb, NULL);
130 else if(state == PA_CONTEXT_FAILED)
132 ///Pulseaudio crashed?
133 paControl->setState(false);
137 qDebug()<<"PA state: "<<(int) state;
141 static void pa_subscribed_events_cb(pa_context *c, enum pa_subscription_event_type t, uint32_t , void *)
143 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
145 case PA_SUBSCRIPTION_EVENT_SINK:
146 qDebug("PA_SUBSCRIPTION_EVENT_SINK event triggered");
147 foreach(PADevice* dev, paControl->sinkList)
151 paControl->sinkList.clear();
153 pa_context_get_sink_info_list(c, pa_sinklist_cb, NULL);
155 case PA_SUBSCRIPTION_EVENT_SOURCE:
156 qDebug("PA_SUBSCRIPTION_EVENT_SOURCE event triggered");
157 foreach(PADevice* dev, paControl->sourceList)
161 paControl->sourceList.clear();
163 pa_context_get_source_info_list(c, pa_sourcelist_cb, NULL);
165 case PA_SUBSCRIPTION_EVENT_MODULE:
166 qDebug("PA_SUBSCRIPTION_EVENT_MODULE event triggered");
167 foreach(PAModule* dev, paControl->moduleList)
171 paControl->moduleList.clear();
173 pa_context_get_module_info_list(c, pa_modulelist_cb, NULL);
178 PAControl::PAControl(QObject *parent)
187 PAControl::~PAControl()
191 foreach (PADevice *source, sourceList) {
192 qDebug() << QString("delete source");
195 foreach (PADevice *sink, sinkList) {
196 qDebug() << QString("delete sink");
199 foreach (PAModule *module, moduleList) {
200 qDebug() << QString("delete module");
204 qDebug() << QString("~PAControl()");
208 PAControl* PAControl::instance()
213 void PAControl::paInit() {
214 qDebug() << "paInit()";
216 // Create a mainloop API and connection to the default server
218 pa_ml = pa_glib_mainloop_new(NULL);
219 pa_ctx = pa_context_new(
220 pa_glib_mainloop_get_api(pa_ml),
223 // This function connects to the pulse server
224 if (pa_context_connect(pa_ctx,
226 PA_CONTEXT_NOFAIL, NULL) < 0)
228 qCritical("PulseAudioService: pa_context_connect() failed");
235 m_audioRouted = false;
236 m_btSourceReady =false;
237 m_btSinkReady = false;
239 pa_context_set_state_callback(pa_ctx, pa_state_cb, NULL);
242 void PAControl::paCleanup() {
243 qDebug() << "paCleanup()";
245 pa_context_disconnect(pa_ctx);
247 pa_context_unref(pa_ctx);
250 void PAControl::setState(bool state)
259 void PAControl::addRef()
264 void PAControl::release()
267 Q_ASSERT(m_refCounter >= 0);
270 void PAControl::reconnect() {
271 qDebug() << "Pulseaudio: reconnect()";
274 paControl = new PAControl();
279 PADevice* PAControl::findBluezSource() {
281 if (sourceList.size() == 0)
284 pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, NULL);
285 if(pa_op) pa_operation_unref(pa_op);
290 foreach (PADevice *source, sourceList) {
291 QString name = source->name;
293 if (name.startsWith(QString("bluez_source."), Qt::CaseSensitive)) {
294 qDebug() << QString(" Matched Bluez source: ") << name;
299 qDebug() << QString("Bluez source: none found");
303 PADevice* PAControl::findBluezSink() {
305 if (sinkList.size() == 0)
308 pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, NULL);
309 if(pa_op) pa_operation_unref(pa_op);
314 foreach (PADevice *sink, sinkList) {
315 QString name = sink->name;
317 if (name.startsWith(QString("bluez_sink."), Qt::CaseSensitive)) {
318 qDebug() << QString(" Matched Bluez sink: ") << name;
323 qDebug() << QString("Bluez sink: none found");
327 PADevice* PAControl::findAlsaSource(QString alsasource) {
329 if (sourceList.size() == 0)
332 pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, NULL);
333 if(pa_op) pa_operation_unref(pa_op);
338 foreach (PADevice *source, sourceList) {
339 qDebug() << QString("Alsa source: ") << source->name;
340 QString name = source->name;
342 if (!alsasource.isNull() && !alsasource.isEmpty())
344 // if alsa source name is provided
345 if (alsasource == name)
347 qDebug() << QString(" Matched Alsa source: ") << name;
350 } else if (name.startsWith(QString("alsa_input."), Qt::CaseSensitive) &&
351 name.endsWith(QString("analog-stereo"), Qt::CaseSensitive) &&
352 !name.contains(QString("timb"))) {
353 // this is default behavior, it will try to look up one
354 qDebug() << QString(" Matched Alsa source: ") << name;
359 qDebug() << QString("Alsa source: none found");
363 PADevice* PAControl::findAlsaSink(QString alsasink) {
365 if (sinkList.size() == 0)
368 pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, NULL);
369 if(pa_op) pa_operation_unref(pa_op);
374 foreach (PADevice *sink, sinkList) {
375 qDebug() << QString("Alsa sink: ") << sink->name;
376 QString name = sink->name;
378 if (!alsasink.isNull() && !alsasink.isEmpty())
380 // if alsa sink name is provided
381 if (alsasink == name)
383 qDebug() << QString(" Matched Alsa sink: ") << name;
386 } else if (name.startsWith(QString("alsa_output."), Qt::CaseSensitive) &&
387 name.endsWith(QString("analog-stereo"), Qt::CaseSensitive) &&
388 !name.contains(QString("timb"))) {
389 // this is default behavior, it will try to look up one
390 qDebug() << QString(" Matched Alsa sink: ") << name;
395 qDebug() << QString("Alsa sink: none found");
399 PAModule* PAControl::findModule(QString name, QString pattern) {
401 if (moduleList.size() == 0)
404 pa_op = pa_context_get_module_info_list(pa_ctx, pa_modulelist_cb, NULL);
405 if(pa_op) pa_operation_unref(pa_op);
410 foreach (PAModule *module, moduleList) {
411 if (module->name.contains(name) && module->argument.contains(pattern)) {
412 qDebug() << QString(" Matched module: ") << module->name;
413 qDebug() << QString(" argument: ") << module->argument;
418 qDebug() << QString("Module: none found");
422 QList<PAModule*> PAControl::getAllModules()
424 if (moduleList.size() == 0)
427 pa_op = pa_context_get_module_info_list(pa_ctx, pa_modulelist_cb, NULL);
428 if(pa_op) pa_operation_unref(pa_op);
434 void PAControl::addSource(PADevice* device)
436 foreach(PADevice* dev, sourceList)
438 if(dev->name == device->name)
441 return; /// already exists
445 sourceList.append(device);
446 emit sourceAppeared(device);
449 void PAControl::addSink(PADevice* device)
451 foreach(PADevice* dev, sinkList)
453 if(dev->name == device->name)
456 return; /// already exists
460 sinkList.append(device);
461 emit sinkAppeared(device);
464 void PAControl::addModule(PAModule *module)
466 foreach(PAModule* dev, moduleList)
468 if(dev->name == module->name && dev->index == module->index)
471 return; /// already exists
475 moduleList.append(module);
476 emit moduleAppeared(module);
479 void PAControl::routeSourceWithSink(PADevice *source, PADevice *sink) {
480 qDebug() << "Routing from " << source->name << " to " << sink->name;
482 if (source != NULL && sink != NULL) {
483 QString arg = "source=\"" % source->name % "\" sink=\"" % sink->name % "\"";
485 pa_op = pa_context_load_module(pa_ctx, "module-loopback", arg.toAscii().data(), module_callback, NULL);
486 if(pa_op) pa_operation_unref(pa_op);
488 qDebug() << QString("load-module module-loopback ") << arg;
492 void PAControl::toggleMuteSource(PADevice *source, bool isMute) {
494 if (source != NULL) {
495 pa_op =pa_context_set_source_mute_by_name(pa_ctx, source->name.toAscii().data(), isMute, operation_callback, NULL);
496 if(pa_op) pa_operation_unref(pa_op);
498 qDebug() << QString("set source mute ") << source->name << QString(" to ") << isMute;
502 void PAControl::unloadModule(PAModule* module) {
504 if (module != NULL && module->index >= 0) {
505 pa_op = pa_context_unload_module(pa_ctx, module->index, operation_callback, NULL);
506 if(pa_op) pa_operation_unref(pa_op);
507 qDebug() << QString("unload-module module-loopback ") << QString(module->name) << QString(" at index ") << module->index;
511 PAStatus PAControl::getStatus() {
515 void PAControl::setErrorMsg(QString msg) {
518 this->status = ERROR;
519 this->errorMsg = msg;
523 QString PAControl::getErrorMsg() {
524 return this->errorMsg;
527 void PAControl::onSourceAppeared(PADevice* device) {
529 CallManager *cm = ManagerProxy::instance()->callManager();
530 if (!cm || !cm->isValid())
533 if(cm->callCount() == 0)
535 qDebug() << "no calls active, ignore";
539 if(device->name.contains("bluez_source"))
541 m_btSourceReady = true;
544 if(!m_audioRouted && m_btSourceReady && m_btSinkReady)
546 qDebug() << QString("Route microphone and speakers");
551 void PAControl::onSinkAppeared(PADevice* device) {
552 CallManager *cm = ManagerProxy::instance()->callManager();
553 if (!cm || !cm->isValid())
556 if(cm->callCount() == 0)
558 qDebug() << "no calls active, ignore";
562 if((device)->name.contains("bluez_sink"))
564 m_btSinkReady = true;
567 if(!m_audioRouted && m_btSourceReady && m_btSinkReady)
569 qDebug() << QString("Route microphone and speakers");
574 void PAControl::routeAudio()
583 qDebug() << QString("Audio already routed");
587 if (m_refCounter > 0)
589 qDebug() << "PA callback not finished, retry";
590 QTimer::singleShot(1000, this, SLOT(routeAudio()));
594 qDebug() << QString("Route audio");
595 source = paControl->findBluezSource();
596 sink = paControl->findBluezSink();
598 if(source == NULL || sink == NULL) {
599 qDebug() << QString("Bluez source or speaker not found");
603 QString alsaSourceName = MGConfItem(QString("/apps/dialer/alsasource")).value().toString();
604 QString alsaSinkName = MGConfItem(QString("/apps/dialer/alsasink")).value().toString();
606 mic = paControl->findAlsaSource(alsaSourceName);
607 speaker = paControl->findAlsaSink(alsaSinkName);
609 if (mic != NULL and speaker != NULL)
611 paControl->routeSourceWithSink(source, speaker);
612 paControl->routeSourceWithSink(mic, sink);
613 qDebug() << QString("Create loopback modules successful");
616 qDebug() << QString("Alsa source and speaker not found");
619 m_audioRouted = true;
620 disconnect(this, SIGNAL(sourceAppeared(PADevice*)));
621 disconnect(this, SIGNAL(sinkAppeared(PADevice*)));
624 void PAControl::unrouteAudio()
626 qDebug() << QString("Unroute audio");
627 PAControl* paControl = PAControl::instance();
629 QList<PAModule*> mlist = paControl->getAllModules();
630 foreach(PAModule *module, mlist)
632 if (module->name.contains("module-loopback") &&
633 module->argument.contains("bluez") &&
634 module->argument.contains("alsa")) {
635 qDebug() << QString("Found loopback module, index: ") << module->index;
636 paControl->unloadModule(module);
637 qDebug() << QString("Remove loopback module successful");
641 m_audioRouted = false;
642 m_btSourceReady = false;
643 m_btSinkReady = false;
646 void PAControl::onCallsChanged()
650 CallManager *cm = ManagerProxy::instance()->callManager();
651 if (!cm || !cm->isValid())
653 qDebug("no call manager. Aborting");
657 if (cm->dialingCall() || cm->activeCall() || cm->callCount() > 1)
659 // new call is dialing or phone is picked up
660 qDebug() << "PAControl: new call in progress";
664 qDebug() << QString("Audio already routed");
668 if(m_btSourceReady && m_btSinkReady)
670 qDebug() << QString("Route microphone and speakers");
675 if(this->findBluezSource() != NULL && this->findBluezSink() != NULL)
677 // bt source and sink exists
678 m_btSourceReady = true;
679 m_btSinkReady = true;
680 qDebug() << QString("Route microphone and speakers");
685 //no bt source or sink yet, let's wait until source and sink appears
686 m_btSourceReady = false;
687 m_btSinkReady = false;
688 connect(this, SIGNAL(sourceAppeared(PADevice*)), this, SLOT(onSourceAppeared(PADevice*)));
689 connect(this, SIGNAL(sinkAppeared(PADevice*)), this, SLOT(onSinkAppeared(PADevice*)));
690 qDebug() << QString("Audio not routed yet, wait for bt source and sinks");
694 else if (cm->callCount() <= 0)
696 qDebug() << QString("no more ofono calls");
699 qDebug() << QString("Unroute microphone and speakers");
705 /* Local Variables: */
707 /* c-basic-offset:4 */
708 /* indent-tabs-mode: nil */