Music Hub  ..
A session-wide music playback service
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros
service_implementation.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2013-2014 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  * Jim Hodapp <jim.hodapp@canonical.com>
18  * Ricardo Mendoza <ricardo.mendoza@canonical.com>
19  *
20  * Note: Some of the PulseAudio code was adapted from telepathy-ofono
21  */
22 
23 #include "service_implementation.h"
24 
27 #include "player_configuration.h"
28 #include "player_implementation.h"
29 
30 #include <boost/asio.hpp>
31 
32 #include <string>
33 #include <cstdint>
34 #include <cstring>
35 #include <map>
36 #include <memory>
37 #include <thread>
38 
39 #include <pulse/pulseaudio.h>
40 
41 #include "util/timeout.h"
42 #include "unity_screen_service.h"
43 #include <hybris/media/media_recorder_layer.h>
44 
45 namespace media = core::ubuntu::media;
46 
47 using namespace std;
48 
50 {
52  : resume_key(std::numeric_limits<std::uint32_t>::max()),
53  keep_alive(io_service),
54  disp_cookie(0),
55  pulse_mainloop_api(nullptr),
56  pulse_context(nullptr),
57  headphones_connected(false),
58  a2dp_connected(false),
59  primary_idx(-1),
60  call_monitor(new CallMonitor)
61  {
62  bus = std::shared_ptr<dbus::Bus>(new dbus::Bus(core::dbus::WellKnownBus::session));
63  bus->install_executor(dbus::asio::make_executor(bus, io_service));
64  worker = std::move(std::thread([this]()
65  {
66  bus->run();
67  }));
68 
69  // Spawn pulse watchdog
70  pulse_mainloop = nullptr;
71  pulse_worker = std::move(std::thread([this]()
72  {
73  std::unique_lock<std::mutex> lk(pulse_mutex);
74  pcv.wait(lk,
75  [this]{
76  if (pulse_mainloop != nullptr || pulse_context != nullptr)
77  {
78  // We come from instance death, destroy and create.
79  if (pulse_context != nullptr)
80  {
81  pa_threaded_mainloop_lock(pulse_mainloop);
82  pa_operation *o;
83  o = pa_context_drain(pulse_context,
84  [](pa_context *context, void *userdata)
85  {
86  (void) context;
87 
88  Private *p = reinterpret_cast<Private*>(userdata);
89  pa_threaded_mainloop_signal(p->mainloop(), 0);
90  }, this);
91 
92  if (o)
93  {
94  while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
95  pa_threaded_mainloop_wait(pulse_mainloop);
96 
97  pa_operation_unref(o);
98  }
99 
100  pa_context_set_state_callback(pulse_context, NULL, NULL);
101  pa_context_set_subscribe_callback(pulse_context, NULL, NULL);
102  pa_context_disconnect(pulse_context);
103  pa_context_unref(pulse_context);
104  pulse_context = nullptr;
105  pa_threaded_mainloop_unlock(pulse_mainloop);
106  }
107  }
108 
109  if (pulse_mainloop == nullptr)
110  {
111  pulse_mainloop = pa_threaded_mainloop_new();
112 
113  if (pa_threaded_mainloop_start(pulse_mainloop) != 0)
114  {
115  std::cerr << "Unable to start pulseaudio mainloop, audio output detection will not function" << std::endl;
116  pa_threaded_mainloop_free(pulse_mainloop);
117  pulse_mainloop = nullptr;
118  }
119  }
120 
121  do {
122  create_pulse_context();
123  } while (pulse_context == nullptr);
124 
125  // Wait for next instance death.
126  return false;
127  });
128  }));
129 
130  // Connect the property change signal that will allow media-hub to take appropriate action
131  // when the battery level reaches critical
132  auto stub_service = dbus::Service::use_service(bus, "com.canonical.indicator.power");
133  indicator_power_session = stub_service->object_for_path(dbus::types::ObjectPath("/com/canonical/indicator/power/Battery"));
134  power_level = indicator_power_session->get_property<core::IndicatorPower::PowerLevel>();
135  is_warning = indicator_power_session->get_property<core::IndicatorPower::IsWarning>();
136 
137  // Obtain session with Unity.Screen so that we request state when doing recording
138  auto bus = std::shared_ptr<dbus::Bus>(new dbus::Bus(core::dbus::WellKnownBus::system));
139  bus->install_executor(dbus::asio::make_executor(bus));
140 
141  auto uscreen_stub_service = dbus::Service::use_service(bus, dbus::traits::Service<core::UScreen>::interface_name());
142  uscreen_session = uscreen_stub_service->object_for_path(dbus::types::ObjectPath("/com/canonical/Unity/Screen"));
143 
144  observer = android_media_recorder_observer_new();
145  android_media_recorder_observer_set_cb(observer, &Private::media_recording_started_callback, this);
146  }
147 
149  {
150  release_pulse_context();
151 
152  if (pulse_mainloop != nullptr)
153  {
154  pa_threaded_mainloop_stop(pulse_mainloop);
155  pa_threaded_mainloop_free(pulse_mainloop);
156  pulse_mainloop = nullptr;
157  }
158 
159  bus->stop();
160 
161  if (worker.joinable())
162  worker.join();
163 
164  if (pulse_worker.joinable())
165  pulse_worker.join();
166  }
167 
168  void media_recording_started(bool started)
169  {
170  if (uscreen_session == nullptr)
171  return;
172 
173  if (started)
174  {
175  if (disp_cookie > 0)
176  return;
177 
178  // Make sure we pause all playback sessions so that it doesn't interfere with recorded audio
179  pause_playback();
180 
181  auto result = uscreen_session->invoke_method_synchronously<core::UScreen::keepDisplayOn, int>();
182  if (result.is_error())
183  throw std::runtime_error(result.error().print());
184  disp_cookie = result.value();
185  }
186  else
187  {
188  if (disp_cookie != -1)
189  {
190  timeout(4000, true, [this](){
191  this->uscreen_session->invoke_method_synchronously<core::UScreen::removeDisplayOnRequest, void>(this->disp_cookie);
192  this->disp_cookie = -1;
193  });
194  }
195  }
196  }
197 
198  static void media_recording_started_callback(bool started, void *context)
199  {
200  if (context == nullptr)
201  return;
202 
203  auto p = static_cast<Private*>(context);
204  p->media_recording_started(started);
205  }
206 
207  pa_threaded_mainloop *mainloop()
208  {
209  return pulse_mainloop;
210  }
211 
212  bool is_port_available(pa_card_port_info **ports, uint32_t n_ports, const char *name)
213  {
214  bool ret = false;
215 
216  if (ports != nullptr && n_ports > 0 && name != nullptr)
217  {
218  for (uint32_t i=0; i<n_ports; i++)
219  {
220  if (strstr(ports[i]->name, name) != nullptr && ports[i]->available != PA_PORT_AVAILABLE_NO)
221  {
222  ret = true;
223  break;
224  }
225  }
226  }
227 
228  return ret;
229  }
230 
232  {
233  const pa_operation *o = pa_context_get_card_info_by_index(pulse_context, primary_idx,
234  [](pa_context *context, const pa_card_info *info, int eol, void *userdata)
235  {
236  (void) context;
237  (void) eol;
238 
239  if (info == nullptr || userdata == nullptr)
240  return;
241 
242  Private *p = reinterpret_cast<Private*>(userdata);
243  if (p->is_port_available(info->ports, info->n_ports, "output-wired"))
244  {
245  if (!p->headphones_connected)
246  std::cout << "Wired headphones connected" << std::endl;
247  p->headphones_connected = true;
248  }
249  else if (p->headphones_connected == true)
250  {
251  std::cout << "Wired headphones disconnected" << std::endl;
252  p->headphones_connected = false;
253  p->pause_playback_if_necessary(std::get<0>(p->active_sink));
254  }
255  }, this);
256  (void) o;
257  }
258 
260  {
261  // Catch uninitialized case (active index == -1)
262  if (std::get<0>(active_sink) == -1)
263  return;
264 
265  if (headphones_connected)
266  return;
267 
268  // No headphones/fallback on primary sink? Pause.
269  if (index == primary_idx)
270  pause_playback();
271  }
272 
273  void set_active_sink(const char *name)
274  {
275  const pa_operation *o = pa_context_get_sink_info_by_name(pulse_context, name,
276  [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
277  {
278  (void) context;
279 
280  if (eol)
281  return;
282 
283  Private *p = reinterpret_cast<Private*>(userdata);
284  std::tuple<uint32_t, uint32_t, std::string> new_sink(std::make_tuple(i->index, i->card, i->name));
285 
286  printf("pulsesink: active_sink=('%s',%d,%d) -> ('%s',%d,%d)\n",
287  std::get<2>(p->active_sink).c_str(), std::get<0>(p->active_sink),
288  std::get<1>(p->active_sink), i->name, i->index, i->card);
289 
290  p->pause_playback_if_necessary(i->index);
291  p->active_sink = new_sink;
292  }, this);
293 
294  (void) o;
295  }
296 
298  {
299  const pa_operation *o = pa_context_get_server_info(pulse_context,
300  [](pa_context *context, const pa_server_info *i, void *userdata)
301  {
302  (void) context;
303 
304  Private *p = reinterpret_cast<Private*>(userdata);
305  if (i->default_sink_name != std::get<2>(p->active_sink))
306  p->set_active_sink(i->default_sink_name);
307  p->update_wired_output();
308  }, this);
309 
310  (void) o;
311  }
312 
314  {
315  if (pulse_context != nullptr)
316  return;
317 
318  active_sink = std::make_tuple(-1, -1, "");
319 
320  bool keep_going = true, ok = true;
321 
322  pulse_mainloop_api = pa_threaded_mainloop_get_api(pulse_mainloop);
323  pa_threaded_mainloop_lock(pulse_mainloop);
324 
325  pulse_context = pa_context_new(pulse_mainloop_api, "MediaHubPulseContext");
326  pa_context_set_state_callback(pulse_context,
327  [](pa_context *context, void *userdata)
328  {
329  (void) context;
330  Private *p = reinterpret_cast<Private*>(userdata);
331  // Signals the pa_threaded_mainloop_wait below to proceed
332  pa_threaded_mainloop_signal(p->mainloop(), 0);
333  }, this);
334 
335  if (pulse_context == nullptr)
336  {
337  std::cerr << "Unable to create new pulseaudio context" << std::endl;
338  pa_threaded_mainloop_unlock(pulse_mainloop);
339  return;
340  }
341 
342  pa_context_connect(pulse_context, nullptr, pa_context_flags_t((int) PA_CONTEXT_NOAUTOSPAWN | (int) PA_CONTEXT_NOFAIL), nullptr);
343  pa_threaded_mainloop_wait(pulse_mainloop);
344 
345  while (keep_going)
346  {
347  switch (pa_context_get_state(pulse_context))
348  {
349  case PA_CONTEXT_CONNECTING: // Wait for service to be available
350  case PA_CONTEXT_AUTHORIZING:
351  case PA_CONTEXT_SETTING_NAME:
352  break;
353 
354  case PA_CONTEXT_READY:
355  std::cout << "Pulseaudio connection established." << std::endl;
356  keep_going = false;
357  break;
358 
359  case PA_CONTEXT_FAILED:
360  case PA_CONTEXT_TERMINATED:
361  keep_going = false;
362  ok = false;
363  break;
364 
365  default:
366  std::cerr << "Pulseaudio connection failure: " << pa_strerror(pa_context_errno(pulse_context));
367  keep_going = false;
368  ok = false;
369  }
370 
371  if (keep_going)
372  pa_threaded_mainloop_wait(pulse_mainloop);
373  }
374 
375  if (ok)
376  {
377  pa_context_set_state_callback(pulse_context,
378  [](pa_context *context, void *userdata)
379  {
380  (void) context;
381  (void) userdata;
382  Private *p = reinterpret_cast<Private*>(userdata);
383  std::unique_lock<std::mutex> lk(p->pulse_mutex);
384  switch (pa_context_get_state(context))
385  {
386  case PA_CONTEXT_FAILED:
387  case PA_CONTEXT_TERMINATED:
388  p->pcv.notify_all();
389  break;
390  default:
391  break;
392  }
393  }, this);
394 
395  //FIXME: Get index for "sink.primary", the default onboard card on Touch.
396  pa_context_get_sink_info_by_name(pulse_context, "sink.primary",
397  [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
398  {
399  (void) context;
400 
401  if (eol)
402  return;
403 
404  Private *p = reinterpret_cast<Private*>(userdata);
405  p->primary_idx = i->index;
406  p->update_wired_output();
407  }, this);
408 
409  update_active_sink();
410 
411  pa_context_set_subscribe_callback(pulse_context,
412  [](pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
413  {
414  (void) context;
415  (void) idx;
416 
417  if (userdata == nullptr)
418  return;
419 
420  Private *p = reinterpret_cast<Private*>(userdata);
421  if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
422  {
423  p->update_active_sink();
424  }
425  }, this);
426  pa_context_subscribe(pulse_context, PA_SUBSCRIPTION_MASK_SINK, nullptr, this);
427  }
428  else
429  {
430  std::cerr << "Connection to pulseaudio failed or was dropped." << std::endl;
431  pa_context_unref(pulse_context);
432  pulse_context = nullptr;
433  }
434 
435  pa_threaded_mainloop_unlock(pulse_mainloop);
436  }
437 
439  {
440  if (pulse_context != nullptr)
441  {
442  pa_threaded_mainloop_lock(pulse_mainloop);
443  pa_context_disconnect(pulse_context);
444  pa_context_unref(pulse_context);
445  pa_threaded_mainloop_unlock(pulse_mainloop);
446  pulse_context = nullptr;
447  }
448  }
449 
450  // This holds the key of the multimedia role Player instance that was paused
451  // when the battery level reached 10% or 5%
453  std::thread worker;
454  dbus::Bus::Ptr bus;
455  boost::asio::io_service io_service;
456  boost::asio::io_service::work keep_alive;
457  std::shared_ptr<dbus::Object> indicator_power_session;
458  std::shared_ptr<core::dbus::Property<core::IndicatorPower::PowerLevel>> power_level;
459  std::shared_ptr<core::dbus::Property<core::IndicatorPower::IsWarning>> is_warning;
461  std::shared_ptr<dbus::Object> uscreen_session;
462  MediaRecorderObserver *observer;
463 
464  // Pulse-specific
465  pa_mainloop_api *pulse_mainloop_api;
466  pa_threaded_mainloop *pulse_mainloop;
467  pa_context *pulse_context;
468  std::thread pulse_worker;
469  std::mutex pulse_mutex;
470  std::condition_variable pcv;
473  std::tuple<int, int, std::string> active_sink;
475 
476  // Gets signaled when both the headphone jack is removed or an A2DP device is
477  // disconnected and playback needs pausing. Also gets signaled when recording
478  // begins.
479  core::Signal<void> pause_playback;
480  std::unique_ptr<CallMonitor> call_monitor;
481  std::list<media::Player::PlayerKey> paused_sessions;
482 };
483 
485 {
486  d->power_level->changed().connect([this](const core::IndicatorPower::PowerLevel::ValueType &level)
487  {
488  // When the battery level hits 10% or 5%, pause all multimedia sessions.
489  // Playback will resume when the user clears the presented notification.
490  if (level == "low" || level == "very_low")
491  pause_all_multimedia_sessions();
492  });
493 
494  d->is_warning->changed().connect([this](const core::IndicatorPower::IsWarning::ValueType &notifying)
495  {
496  // If the low battery level notification is no longer being displayed,
497  // resume what the user was previously playing
498  if (!notifying)
499  resume_multimedia_session();
500  });
501 
502  d->pause_playback.connect([this]()
503  {
504  std::cout << "Got pause_playback signal, pausing all multimedia sessions" << std::endl;
505  pause_all_multimedia_sessions();
506  });
507 
508  d->call_monitor->on_change([this](CallMonitor::State state) {
509  switch (state) {
511  std::cout << "Got call started signal, pausing all multimedia sessions" << std::endl;
512  pause_all_multimedia_sessions();
513  break;
514  case CallMonitor::OnHook:
515  std::cout << "Got call ended signal, resuming paused multimedia sessions" << std::endl;
516  resume_paused_multimedia_sessions();
517  break;
518  }
519  });
520 }
521 
523 {
524 }
525 
526 std::shared_ptr<media::Player> media::ServiceImplementation::create_session(
527  const media::Player::Configuration& conf)
528 {
529  auto player = std::make_shared<media::PlayerImplementation>(
530  conf.identity, conf.bus, conf.session, shared_from_this(), conf.key);
531 
532  auto key = conf.key;
533  player->on_client_disconnected().connect([this, key]()
534  {
535  // Call remove_player_for_key asynchronously otherwise deadlock can occur
536  // if called within this dispatcher context.
537  // remove_player_for_key can destroy the player instance which in turn
538  // destroys the "on_client_disconnected" signal whose destructor will wait
539  // until all dispatches are done
540  d->io_service.post([this, key]()
541  {
542  if (!has_player_for_key(key))
543  return;
544 
545  if (player_for_key(key)->lifetime() == Player::Lifetime::normal)
547  });
548  });
549 
550  return player;
551 }
552 
553 std::shared_ptr<media::Player> media::ServiceImplementation::create_fixed_session(const std::string&, const media::Player::Configuration&)
554 {
555  // no impl
556  return std::shared_ptr<media::Player>();
557 }
558 
560 {
561  // no impl
562  return std::shared_ptr<media::Player>();
563 }
564 
566 {
567  if (not has_player_for_key(key))
568  {
569  cerr << "Could not find Player by key: " << key << endl;
570  return;
571  }
572 
573  auto current_player = player_for_key(key);
574 
575  // We immediately make the player known as new current player.
576  if (current_player->audio_stream_role() == media::Player::multimedia)
578 
579  enumerate_players([current_player, key](const media::Player::PlayerKey& other_key, const std::shared_ptr<media::Player>& other_player)
580  {
581  // Only pause a Player if all of the following criteria are met:
582  // 1) currently playing
583  // 2) not the same player as the one passed in my key
584  // 3) new Player has an audio stream role set to multimedia
585  // 4) has an audio stream role set to multimedia
586  if (other_player->playback_status() == Player::playing &&
587  other_key != key &&
588  current_player->audio_stream_role() == media::Player::multimedia &&
589  other_player->audio_stream_role() == media::Player::multimedia)
590  {
591  cout << "Pausing Player with key: " << other_key << endl;
592  other_player->pause();
593  }
594  });
595 }
596 
597 void media::ServiceImplementation::pause_all_multimedia_sessions()
598 {
599  enumerate_players([this](const media::Player::PlayerKey& key, const std::shared_ptr<media::Player>& player)
600  {
601  if (player->playback_status() == Player::playing
602  && player->audio_stream_role() == media::Player::multimedia)
603  {
604  d->paused_sessions.push_back(key);
605  std::cout << "Pausing Player with key: " << key << std::endl;
606  player->pause();
607  }
608  });
609 }
610 
611 void media::ServiceImplementation::resume_paused_multimedia_sessions()
612 {
613  std::for_each(d->paused_sessions.begin(), d->paused_sessions.end(), [this](const media::Player::PlayerKey& key) {
614  player_for_key(key)->play();
615  });
616 
617  d->paused_sessions.clear();
618 }
619 
620 void media::ServiceImplementation::resume_multimedia_session()
621 {
622  if (not has_player_for_key(d->resume_key))
623  return;
624 
625  auto player = player_for_key(d->resume_key);
626 
627  if (player->playback_status() == Player::paused)
628  {
629  cout << "Resuming playback of Player with key: " << d->resume_key << endl;
630  player->play();
631  d->resume_key = std::numeric_limits<std::uint32_t>::max();
632  }
633 }
void set_current_player_for_key(const Player::PlayerKey &key)
bool has_player_for_key(const Player::PlayerKey &key) const
STL namespace.
bool is_port_available(pa_card_port_info **ports, uint32_t n_ports, const char *name)
std::tuple< int, int, std::string > active_sink
void enumerate_players(const PlayerEnumerator &enumerator) const
std::shared_ptr< core::dbus::Property< core::IndicatorPower::PowerLevel > > power_level
static void media_recording_started_callback(bool started, void *context)
void pause_other_sessions(Player::PlayerKey key)
std::shared_ptr< Player > create_session(const Player::Configuration &)
std::shared_ptr< Player > resume_session(Player::PlayerKey key)
void remove_player_for_key(const Player::PlayerKey &key)
std::shared_ptr< core::dbus::Property< core::IndicatorPower::IsWarning > > is_warning
std::shared_ptr< dbus::Object > uscreen_session
std::shared_ptr< Player > player_for_key(const Player::PlayerKey &key) const
std::shared_ptr< dbus::Object > indicator_power_session
std::list< media::Player::PlayerKey > paused_sessions
std::unique_ptr< CallMonitor > call_monitor
std::shared_ptr< Player > create_fixed_session(const std::string &name, const Player::Configuration &)