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  auto result = uscreen_session->invoke_method_synchronously<core::UScreen::keepDisplayOn, int>();
179  if (result.is_error())
180  throw std::runtime_error(result.error().print());
181  disp_cookie = result.value();
182  }
183  else
184  {
185  if (disp_cookie != -1)
186  {
187  timeout(4000, true, [this](){
188  this->uscreen_session->invoke_method_synchronously<core::UScreen::removeDisplayOnRequest, void>(this->disp_cookie);
189  this->disp_cookie = -1;
190  });
191  }
192  }
193  }
194 
195  static void media_recording_started_callback(bool started, void *context)
196  {
197  if (context == nullptr)
198  return;
199 
200  auto p = static_cast<Private*>(context);
201  p->media_recording_started(started);
202  }
203 
204  pa_threaded_mainloop *mainloop()
205  {
206  return pulse_mainloop;
207  }
208 
209  bool is_port_available(pa_card_port_info **ports, uint32_t n_ports, const char *name)
210  {
211  bool ret = false;
212 
213  if (ports != nullptr && n_ports > 0 && name != nullptr)
214  {
215  for (uint32_t i=0; i<n_ports; i++)
216  {
217  if (strstr(ports[i]->name, name) != nullptr && ports[i]->available != PA_PORT_AVAILABLE_NO)
218  {
219  ret = true;
220  break;
221  }
222  }
223  }
224 
225  return ret;
226  }
227 
229  {
230  const pa_operation *o = pa_context_get_card_info_by_index(pulse_context, primary_idx,
231  [](pa_context *context, const pa_card_info *info, int eol, void *userdata)
232  {
233  (void) context;
234  (void) eol;
235 
236  if (info == nullptr || userdata == nullptr)
237  return;
238 
239  Private *p = reinterpret_cast<Private*>(userdata);
240  if (p->is_port_available(info->ports, info->n_ports, "output-wired"))
241  {
242  if (!p->headphones_connected)
243  std::cout << "Wired headphones connected" << std::endl;
244  p->headphones_connected = true;
245  }
246  else if (p->headphones_connected == true)
247  {
248  std::cout << "Wired headphones disconnected" << std::endl;
249  p->headphones_connected = false;
250  p->pause_playback_if_necessary(std::get<0>(p->active_sink));
251  }
252  }, this);
253  (void) o;
254  }
255 
257  {
258  // Catch uninitialized case (active index == -1)
259  if (std::get<0>(active_sink) == -1)
260  return;
261 
262  if (headphones_connected)
263  return;
264 
265  // No headphones/fallback on primary sink? Pause.
266  if (index == primary_idx)
267  pause_playback();
268  }
269 
270  void set_active_sink(const char *name)
271  {
272  const pa_operation *o = pa_context_get_sink_info_by_name(pulse_context, name,
273  [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
274  {
275  (void) context;
276 
277  if (eol)
278  return;
279 
280  Private *p = reinterpret_cast<Private*>(userdata);
281  std::tuple<uint32_t, uint32_t, std::string> new_sink(std::make_tuple(i->index, i->card, i->name));
282 
283  printf("pulsesink: active_sink=('%s',%d,%d) -> ('%s',%d,%d)\n",
284  std::get<2>(p->active_sink).c_str(), std::get<0>(p->active_sink),
285  std::get<1>(p->active_sink), i->name, i->index, i->card);
286 
287  p->pause_playback_if_necessary(i->index);
288  p->active_sink = new_sink;
289  }, this);
290 
291  (void) o;
292  }
293 
295  {
296  const pa_operation *o = pa_context_get_server_info(pulse_context,
297  [](pa_context *context, const pa_server_info *i, void *userdata)
298  {
299  (void) context;
300 
301  Private *p = reinterpret_cast<Private*>(userdata);
302  if (i->default_sink_name != std::get<2>(p->active_sink))
303  p->set_active_sink(i->default_sink_name);
304  p->update_wired_output();
305  }, this);
306 
307  (void) o;
308  }
309 
311  {
312  if (pulse_context != nullptr)
313  return;
314 
315  active_sink = std::make_tuple(-1, -1, "");
316 
317  bool keep_going = true, ok = true;
318 
319  pulse_mainloop_api = pa_threaded_mainloop_get_api(pulse_mainloop);
320  pa_threaded_mainloop_lock(pulse_mainloop);
321 
322  pulse_context = pa_context_new(pulse_mainloop_api, "MediaHubPulseContext");
323  pa_context_set_state_callback(pulse_context,
324  [](pa_context *context, void *userdata)
325  {
326  (void) context;
327  Private *p = reinterpret_cast<Private*>(userdata);
328  // Signals the pa_threaded_mainloop_wait below to proceed
329  pa_threaded_mainloop_signal(p->mainloop(), 0);
330  }, this);
331 
332  if (pulse_context == nullptr)
333  {
334  std::cerr << "Unable to create new pulseaudio context" << std::endl;
335  pa_threaded_mainloop_unlock(pulse_mainloop);
336  return;
337  }
338 
339  pa_context_connect(pulse_context, nullptr, pa_context_flags_t((int) PA_CONTEXT_NOAUTOSPAWN | (int) PA_CONTEXT_NOFAIL), nullptr);
340  pa_threaded_mainloop_wait(pulse_mainloop);
341 
342  while (keep_going)
343  {
344  switch (pa_context_get_state(pulse_context))
345  {
346  case PA_CONTEXT_CONNECTING: // Wait for service to be available
347  case PA_CONTEXT_AUTHORIZING:
348  case PA_CONTEXT_SETTING_NAME:
349  break;
350 
351  case PA_CONTEXT_READY:
352  std::cout << "Pulseaudio connection established." << std::endl;
353  keep_going = false;
354  break;
355 
356  case PA_CONTEXT_FAILED:
357  case PA_CONTEXT_TERMINATED:
358  keep_going = false;
359  ok = false;
360  break;
361 
362  default:
363  std::cerr << "Pulseaudio connection failure: " << pa_strerror(pa_context_errno(pulse_context));
364  keep_going = false;
365  ok = false;
366  }
367 
368  if (keep_going)
369  pa_threaded_mainloop_wait(pulse_mainloop);
370  }
371 
372  if (ok)
373  {
374  pa_context_set_state_callback(pulse_context,
375  [](pa_context *context, void *userdata)
376  {
377  (void) context;
378  (void) userdata;
379  Private *p = reinterpret_cast<Private*>(userdata);
380  std::unique_lock<std::mutex> lk(p->pulse_mutex);
381  switch (pa_context_get_state(context))
382  {
383  case PA_CONTEXT_FAILED:
384  case PA_CONTEXT_TERMINATED:
385  p->pcv.notify_all();
386  break;
387  default:
388  break;
389  }
390  }, this);
391 
392  //FIXME: Get index for "sink.primary", the default onboard card on Touch.
393  pa_context_get_sink_info_by_name(pulse_context, "sink.primary",
394  [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
395  {
396  (void) context;
397 
398  if (eol)
399  return;
400 
401  Private *p = reinterpret_cast<Private*>(userdata);
402  p->primary_idx = i->index;
403  p->update_wired_output();
404  }, this);
405 
406  update_active_sink();
407 
408  pa_context_set_subscribe_callback(pulse_context,
409  [](pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
410  {
411  (void) context;
412  (void) idx;
413 
414  if (userdata == nullptr)
415  return;
416 
417  Private *p = reinterpret_cast<Private*>(userdata);
418  if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
419  {
420  p->update_active_sink();
421  }
422  }, this);
423  pa_context_subscribe(pulse_context, PA_SUBSCRIPTION_MASK_SINK, nullptr, this);
424  }
425  else
426  {
427  std::cerr << "Connection to pulseaudio failed or was dropped." << std::endl;
428  pa_context_unref(pulse_context);
429  pulse_context = nullptr;
430  }
431 
432  pa_threaded_mainloop_unlock(pulse_mainloop);
433  }
434 
436  {
437  if (pulse_context != nullptr)
438  {
439  pa_threaded_mainloop_lock(pulse_mainloop);
440  pa_context_disconnect(pulse_context);
441  pa_context_unref(pulse_context);
442  pa_threaded_mainloop_unlock(pulse_mainloop);
443  pulse_context = nullptr;
444  }
445  }
446 
447  // This holds the key of the multimedia role Player instance that was paused
448  // when the battery level reached 10% or 5%
450  std::thread worker;
451  dbus::Bus::Ptr bus;
452  boost::asio::io_service io_service;
453  boost::asio::io_service::work keep_alive;
454  std::shared_ptr<dbus::Object> indicator_power_session;
455  std::shared_ptr<core::dbus::Property<core::IndicatorPower::PowerLevel>> power_level;
456  std::shared_ptr<core::dbus::Property<core::IndicatorPower::IsWarning>> is_warning;
458  std::shared_ptr<dbus::Object> uscreen_session;
459  MediaRecorderObserver *observer;
460 
461  // Pulse-specific
462  pa_mainloop_api *pulse_mainloop_api;
463  pa_threaded_mainloop *pulse_mainloop;
464  pa_context *pulse_context;
465  std::thread pulse_worker;
466  std::mutex pulse_mutex;
467  std::condition_variable pcv;
470  std::tuple<int, int, std::string> active_sink;
472 
473  // Gets signaled when both the headphone jack is removed or an A2DP device is
474  // disconnected and playback needs pausing
475  core::Signal<void> pause_playback;
476  std::unique_ptr<CallMonitor> call_monitor;
477  std::list<media::Player::PlayerKey> paused_sessions;
478 };
479 
481 {
482  d->power_level->changed().connect([this](const core::IndicatorPower::PowerLevel::ValueType &level)
483  {
484  // When the battery level hits 10% or 5%, pause all multimedia sessions.
485  // Playback will resume when the user clears the presented notification.
486  if (level == "low" || level == "very_low")
487  pause_all_multimedia_sessions();
488  });
489 
490  d->is_warning->changed().connect([this](const core::IndicatorPower::IsWarning::ValueType &notifying)
491  {
492  // If the low battery level notification is no longer being displayed,
493  // resume what the user was previously playing
494  if (!notifying)
495  resume_multimedia_session();
496  });
497 
498  d->pause_playback.connect([this]()
499  {
500  std::cout << "Got pause_playback signal, pausing all multimedia sessions" << std::endl;
501  pause_all_multimedia_sessions();
502  });
503 
504  d->call_monitor->on_change([this](CallMonitor::State state) {
505  switch (state) {
507  pause_all_multimedia_sessions();
508  break;
509  case CallMonitor::OnHook:
510  resume_paused_multimedia_sessions();
511  break;
512  }
513  });
514 }
515 
517 {
518 }
519 
520 std::shared_ptr<media::Player> media::ServiceImplementation::create_session(
521  const media::Player::Configuration& conf)
522 {
523  auto player = std::make_shared<media::PlayerImplementation>(
524  conf.identity, conf.bus, conf.session, shared_from_this(), conf.key);
525 
526  auto key = conf.key;
527  player->on_client_disconnected().connect([this, key]()
528  {
529  // Call remove_player_for_key asynchronously otherwise deadlock can occur
530  // if called within this dispatcher context.
531  // remove_player_for_key can destroy the player instance which in turn
532  // destroys the "on_client_disconnected" signal whose destructor will wait
533  // until all dispatches are done
534  d->io_service.post([this, key]()
535  {
537  });
538  });
539 
540  return player;
541 }
542 
544 {
545  if (not has_player_for_key(key))
546  {
547  cerr << "Could not find Player by key: " << key << endl;
548  return;
549  }
550 
551  auto current_player = player_for_key(key);
552 
553  // We immediately make the player known as new current player.
554  if (current_player->audio_stream_role() == media::Player::multimedia)
556 
557  enumerate_players([current_player, key](const media::Player::PlayerKey& other_key, const std::shared_ptr<media::Player>& other_player)
558  {
559  // Only pause a Player if all of the following criteria are met:
560  // 1) currently playing
561  // 2) not the same player as the one passed in my key
562  // 3) new Player has an audio stream role set to multimedia
563  // 4) has an audio stream role set to multimedia
564  if (other_player->playback_status() == Player::playing &&
565  other_key != key &&
566  current_player->audio_stream_role() == media::Player::multimedia &&
567  other_player->audio_stream_role() == media::Player::multimedia)
568  {
569  cout << "Pausing Player with key: " << other_key << endl;
570  other_player->pause();
571  }
572  });
573 }
574 
575 void media::ServiceImplementation::pause_all_multimedia_sessions()
576 {
577  enumerate_players([this](const media::Player::PlayerKey& key, const std::shared_ptr<media::Player>& player)
578  {
579  if (player->playback_status() == Player::playing
580  && player->audio_stream_role() == media::Player::multimedia)
581  {
582  d->paused_sessions.push_back(key);
583  player->pause();
584  }
585  });
586 }
587 
588 void media::ServiceImplementation::resume_paused_multimedia_sessions()
589 {
590  std::for_each(d->paused_sessions.begin(), d->paused_sessions.end(), [this](const media::Player::PlayerKey& key) {
591  player_for_key(key)->play();
592  });
593 
594  d->paused_sessions.clear();
595 }
596 
597 void media::ServiceImplementation::resume_multimedia_session()
598 {
599  if (not has_player_for_key(d->resume_key))
600  return;
601 
602  auto player = player_for_key(d->resume_key);
603 
604  if (player->playback_status() == Player::paused)
605  {
606  cout << "Resuming playback of Player with key: " << d->resume_key << endl;
607  player->play();
608  d->resume_key = std::numeric_limits<std::uint32_t>::max();
609  }
610 }
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 &)
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