Merge lp:~phablet-team/media-hub/introduce-audio-output-observer-interface into lp:media-hub

Proposed by Thomas Voß
Status: Merged
Approved by: Ricardo Mendoza
Approved revision: 133
Merged at revision: 121
Proposed branch: lp:~phablet-team/media-hub/introduce-audio-output-observer-interface
Merge into: lp:media-hub
Prerequisite: lp:~thomas-voss/media-hub/introduce-battery-observer-interface
Diff against target: 1333 lines (+871/-360)
9 files modified
src/core/media/CMakeLists.txt (+4/-0)
src/core/media/audio/ostream_reporter.cpp (+54/-0)
src/core/media/audio/ostream_reporter.h (+55/-0)
src/core/media/audio/output_observer.cpp (+50/-0)
src/core/media/audio/output_observer.h (+76/-0)
src/core/media/audio/pulse_audio_output_observer.cpp (+467/-0)
src/core/media/audio/pulse_audio_output_observer.h (+128/-0)
src/core/media/gstreamer/playbin.cpp (+4/-1)
src/core/media/service_implementation.cpp (+33/-359)
To merge this branch: bzr merge lp:~phablet-team/media-hub/introduce-audio-output-observer-interface
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Thomas Voß (community) Needs Fixing
Jim Hodapp (community) code Needs Fixing
Review via email: mp+242914@code.launchpad.net

Commit message

Introduce an interface media::audio::OutputObserver that allows the core classes to observer the state of external audio outputs (headphones/headsets).
Provide an implementation of media::audio::OutputObserver that relies on Pulseaudio to monitor the availability of ports on the default sink.
Adjust media::ServiceImplementation to use media::audio::OutputObserver, defaulting to media::audio::PulseAudioOutputObserver.

Description of the change

Introduce an interface media::audio::OutputObserver that allows the core classes to observer the state of external audio outputs (headphones/headsets).
Provide an implementation of media::audio::OutputObserver that relies on Pulseaudio to monitor the availability of ports on the default sink.
Adjust media::ServiceImplementation to use media::audio::OutputObserver, defaulting to media::audio::PulseAudioOutputObserver.

To post a comment you must log in.
108. By Thomas Voß

Remerge prereq branch.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Jim Hodapp (jhodapp) wrote :

A few comments below.

review: Needs Fixing (code)
109. By Thomas Voß

[ Jim Hodapp ]
* Resubmitting with prerequisite branch (LP: #1331041)
[ Justin McPherson ]
* Resubmitting with prerequisite branch (LP: #1331041)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
110. By Thomas Voß

Merge prerequisite branch.

Revision history for this message
Thomas Voß (thomas-voss) :
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
111. By Thomas Voß

[ Jim Hodapp ]
* Error reporting all the way up to the app level from the playbin
  pipeline.
[ Ubuntu daily release ]
* New rebuild forced
[ Jim Hodapp ]
* Don't auto-resume playback of videos after a phone call ends. (LP:
  #1411273)
[ Ubuntu daily release ]
* New rebuild forced
[ Ricardo Salveti de Araujo ]
* service_implementation: adding debug for call started/ended signals.
  Make sure account and connection are available when setting up
  account manager (patch from Gustavo Boiko). call_monitor: don't
  check caps when hooking up on/off signals, until bug 1409125 is
  fixed. Enable parallel building . (LP: #1409125)
[ Jim Hodapp ]
* Pause playback when recording begins. (LP: #1398047)
[ Ricardo Salveti de Araujo ]
* call_monitor.cpp: waiting for bridge to be up, and also protecting
  the on_change call (LP: #1408137)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
112. By Thomas Voß

Merge prereq branch.

113. By Thomas Voß

Fix FTBFS.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
114. By Thomas Voß

Merge prereq branch.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
115. By Thomas Voß

Add reporting facilities to the PulseAudioOutputObsever implementation.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
116. By Thomas Voß

Consider the adjusted configuration when creating the audio::OutputObserver instance.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
117. By Thomas Voß

Only subscribe to relevant ports on sink.

118. By Thomas Voß

Make the Pulse-based AudioOutputObserver implementation more verbose.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
119. By Thomas Voß

Be explicit about handling state PA_PORT_AVAILABLE_UNKNOWN.
Add log line to the gstreamer::Engine to capture if a requested state change has been successfully enqueued.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
120. By Thomas Voß

Monitor a2dp headphone output.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
121. By Thomas Voß

Correctly calculated "monitored" for log output.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
122. By Thomas Voß

Bundle together port properties in a struct Port for reporting purposes.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
123. By Thomas Voß

More logging.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
124. By Thomas Voß

Log result of determining whether a port is available on a sink.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
125. By Thomas Voß

Adjust regex for handling ports.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
126. By Thomas Voß

Merge prereq branch.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
127. By Thomas Voß

off really should be on.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
128. By Ricardo Mendoza

Rewrite observer to deal in Public/Private absolutes, always pausing when Private media goes Public.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Thomas Voß (thomas-voss) wrote :

A minor niggle inline.

review: Needs Fixing
129. By Ricardo Mendoza

Rework public/private into earpiece/speaker/external for better state tracking

130. By Ricardo Mendoza

Wrong type

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
131. By Ricardo Mendoza

Dont pause when disconecting headphones, to conform to mh2 behaviour

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
132. By Thomas Voß

* debian/control:
  - Removing pre-depends that are not required
  - Bumping standards-version to 3.9.6
[ Ricardo Salveti de Araujo ]
* Migrating tests to use ogg instead of mp3/avi removed:
  tests/h264.avi tests/test.mp3 added: tests/test-audio-1.ogg
  tests/test-video.ogg tests/test.mp3 renamed: tests/test.ogg =>
  tests/test-audio.ogg

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
133. By Ricardo Mendoza

Initialize primary and active sink to the non-initialized flag

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/core/media/CMakeLists.txt'
2--- src/core/media/CMakeLists.txt 2015-03-19 00:04:24 +0000
3+++ src/core/media/CMakeLists.txt 2015-03-19 00:04:25 +0000
4@@ -91,6 +91,10 @@
5 cover_art_resolver.cpp
6 engine.cpp
7
8+ audio/pulse_audio_output_observer.cpp
9+ audio/ostream_reporter.cpp
10+ audio/output_observer.cpp
11+
12 power/battery_observer.cpp
13 power/state_controller.cpp
14
15
16=== added directory 'src/core/media/audio'
17=== added file 'src/core/media/audio/ostream_reporter.cpp'
18--- src/core/media/audio/ostream_reporter.cpp 1970-01-01 00:00:00 +0000
19+++ src/core/media/audio/ostream_reporter.cpp 2015-03-19 00:04:25 +0000
20@@ -0,0 +1,54 @@
21+/*
22+ * Copyright © 2014 Canonical Ltd.
23+ *
24+ * This program is free software: you can redistribute it and/or modify it
25+ * under the terms of the GNU Lesser General Public License version 3,
26+ * as published by the Free Software Foundation.
27+ *
28+ * This program is distributed in the hope that it will be useful,
29+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
30+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31+ * GNU Lesser General Public License for more details.
32+ *
33+ * You should have received a copy of the GNU Lesser General Public License
34+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
35+ *
36+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
37+ */
38+
39+#include <core/media/audio/ostream_reporter.h>
40+
41+namespace audio = core::ubuntu::media::audio;
42+
43+audio::OStreamReporter::OStreamReporter(std::ostream &out) : out{out}
44+{
45+}
46+
47+void audio::OStreamReporter::connected_to_pulse_audio()
48+{
49+ out << "Connection to PulseAudio has been successfully established." << std::endl;
50+}
51+
52+void audio::OStreamReporter::query_for_default_sink_failed()
53+{
54+ out << "Query for default sink failed." << std::endl;
55+}
56+
57+void audio::OStreamReporter::query_for_default_sink_finished(const std::string& sink_name)
58+{
59+ out << "Default PulseAudio sync has been identified: " << sink_name << std::endl;
60+}
61+
62+void audio::OStreamReporter::query_for_sink_info_finished(const std::string& name, std::uint32_t index, const std::set<Port>& known_ports)
63+{
64+ out << "PulseAudio sink details for " << name << " with index " << index << " is available:" << std::endl;
65+ for (const auto& port : known_ports)
66+ {
67+ if (port.is_monitored)
68+ out << " " << port.description << ": " << std::boolalpha << port.is_available << "\n";
69+ }
70+}
71+void audio::OStreamReporter::sink_event_with_index(std::uint32_t index)
72+{
73+ out << "PulseAudio event for sink with index " << index << " received." << std::endl;
74+}
75
76=== added file 'src/core/media/audio/ostream_reporter.h'
77--- src/core/media/audio/ostream_reporter.h 1970-01-01 00:00:00 +0000
78+++ src/core/media/audio/ostream_reporter.h 2015-03-19 00:04:25 +0000
79@@ -0,0 +1,55 @@
80+/*
81+ * Copyright © 2014 Canonical Ltd.
82+ *
83+ * This program is free software: you can redistribute it and/or modify it
84+ * under the terms of the GNU Lesser General Public License version 3,
85+ * as published by the Free Software Foundation.
86+ *
87+ * This program is distributed in the hope that it will be useful,
88+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
89+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
90+ * GNU Lesser General Public License for more details.
91+ *
92+ * You should have received a copy of the GNU Lesser General Public License
93+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
94+ *
95+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
96+ */
97+#ifndef CORE_UBUNTU_MEDIA_AUDIO_OSTREAM_REPORTER_H_
98+#define CORE_UBUNTU_MEDIA_AUDIO_OSTREAM_REPORTER_H_
99+
100+#include <core/media/audio/pulse_audio_output_observer.h>
101+
102+#include <iosfwd>
103+
104+namespace core
105+{
106+namespace ubuntu
107+{
108+namespace media
109+{
110+namespace audio
111+{
112+// A PulseAudioOutputObserver::Reporter implementation printing events to
113+// the configured output stream.
114+class OStreamReporter : public PulseAudioOutputObserver::Reporter
115+{
116+public:
117+ // Constructs a new reporter instance, outputting events to the given stream.
118+ OStreamReporter(std::ostream& out = std::cout);
119+
120+ void connected_to_pulse_audio() override;
121+ void query_for_default_sink_failed() override;
122+ void query_for_default_sink_finished(const std::string& sink_name) override;
123+ void query_for_sink_info_finished(const std::string& name, std::uint32_t index, const std::set<Port>& known_ports) override;
124+ void sink_event_with_index(std::uint32_t index) override;
125+
126+private:
127+ std::ostream& out;
128+};
129+}
130+}
131+}
132+}
133+
134+#endif // CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OSTREAM_REPORTER_H_
135
136=== added file 'src/core/media/audio/output_observer.cpp'
137--- src/core/media/audio/output_observer.cpp 1970-01-01 00:00:00 +0000
138+++ src/core/media/audio/output_observer.cpp 2015-03-19 00:04:25 +0000
139@@ -0,0 +1,50 @@
140+/*
141+ * Copyright © 2014 Canonical Ltd.
142+ *
143+ * This program is free software: you can redistribute it and/or modify it
144+ * under the terms of the GNU Lesser General Public License version 3,
145+ * as published by the Free Software Foundation.
146+ *
147+ * This program is distributed in the hope that it will be useful,
148+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
149+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
150+ * GNU Lesser General Public License for more details.
151+ *
152+ * You should have received a copy of the GNU Lesser General Public License
153+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
154+ *
155+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
156+ */
157+
158+#include <core/media/audio/output_observer.h>
159+
160+#include <core/media/audio/pulse_audio_output_observer.h>
161+#include <core/media/audio/ostream_reporter.h>
162+
163+#include <iostream>
164+
165+namespace audio = core::ubuntu::media::audio;
166+
167+std::ostream& audio::operator<<(std::ostream& out, audio::OutputState state)
168+{
169+ switch (state)
170+ {
171+ case audio::OutputState::Earpiece:
172+ return out << "OutputState::Earpiece";
173+ case audio::OutputState::Speaker:
174+ return out << "OutputState::Speaker";
175+ case audio::OutputState::External:
176+ return out << "OutputState::External";
177+ }
178+
179+ return out;
180+}
181+
182+audio::OutputObserver::Ptr audio::make_platform_default_output_observer()
183+{
184+ audio::PulseAudioOutputObserver::Configuration config;
185+ config.sink = "sink.primary";
186+ config.output_port_patterns = {std::regex{"output-wired_head.*|output-a2dp_headphones"}};
187+ config.reporter = std::make_shared<audio::OStreamReporter>();
188+ return std::make_shared<audio::PulseAudioOutputObserver>(config);
189+}
190
191=== added file 'src/core/media/audio/output_observer.h'
192--- src/core/media/audio/output_observer.h 1970-01-01 00:00:00 +0000
193+++ src/core/media/audio/output_observer.h 2015-03-19 00:04:25 +0000
194@@ -0,0 +1,76 @@
195+/*
196+ * Copyright © 2014 Canonical Ltd.
197+ *
198+ * This program is free software: you can redistribute it and/or modify it
199+ * under the terms of the GNU Lesser General Public License version 3,
200+ * as published by the Free Software Foundation.
201+ *
202+ * This program is distributed in the hope that it will be useful,
203+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
204+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
205+ * GNU Lesser General Public License for more details.
206+ *
207+ * You should have received a copy of the GNU Lesser General Public License
208+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
209+ *
210+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
211+ */
212+#ifndef CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OBSERVER_H_
213+#define CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OBSERVER_H_
214+
215+#include <core/property.h>
216+
217+#include <iosfwd>
218+#include <memory>
219+
220+namespace core
221+{
222+namespace ubuntu
223+{
224+namespace media
225+{
226+namespace audio
227+{
228+// All known states of an audio output.
229+enum class OutputState
230+{
231+ // The output is via a private earpiece (i.e. headphones, headset)
232+ Earpiece,
233+ // The output is via the internal speaker.
234+ Speaker,
235+ // The output is via an external device (a2dp, etc)
236+ External,
237+};
238+
239+// Models observation of audio outputs of a device.
240+// Right now, we are only interested in monitoring the
241+// state of external outputs to react accordingly if
242+// wired or bluetooth outputs are connected/disconnected.
243+class OutputObserver
244+{
245+public:
246+ // Save us some typing.
247+ typedef std::shared_ptr<OutputObserver> Ptr;
248+
249+ virtual ~OutputObserver() = default;
250+
251+ // Getable/observable property holding the state of external outputs.
252+ virtual const core::Property<OutputState>& external_output_state() const = 0;
253+
254+protected:
255+ OutputObserver() = default;
256+ OutputObserver(const OutputObserver&) = delete;
257+ OutputObserver& operator=(const OutputObserver&) = delete;
258+};
259+
260+// Pretty prints the given state to the given output stream.
261+std::ostream& operator<<(std::ostream&, OutputState);
262+
263+// Creats a platform default instance for observing audio outputs.
264+OutputObserver::Ptr make_platform_default_output_observer();
265+}
266+}
267+}
268+}
269+
270+#endif // CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OBSERVER_H_
271
272=== added file 'src/core/media/audio/pulse_audio_output_observer.cpp'
273--- src/core/media/audio/pulse_audio_output_observer.cpp 1970-01-01 00:00:00 +0000
274+++ src/core/media/audio/pulse_audio_output_observer.cpp 2015-03-19 00:04:25 +0000
275@@ -0,0 +1,467 @@
276+/*
277+ * Copyright © 2014 Canonical Ltd.
278+ *
279+ * This program is free software: you can redistribute it and/or modify it
280+ * under the terms of the GNU Lesser General Public License version 3,
281+ * as published by the Free Software Foundation.
282+ *
283+ * This program is distributed in the hope that it will be useful,
284+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
285+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
286+ * GNU Lesser General Public License for more details.
287+ *
288+ * You should have received a copy of the GNU Lesser General Public License
289+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
290+ *
291+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
292+ * Ricardo Mendoza <ricardo.mendoza@canonical.com>
293+ */
294+
295+#include <core/media/audio/pulse_audio_output_observer.h>
296+
297+#include <pulse/pulseaudio.h>
298+
299+#include <cstdint>
300+
301+#include <map>
302+#include <regex>
303+#include <string>
304+
305+namespace audio = core::ubuntu::media::audio;
306+
307+namespace
308+{
309+// We wrap calls to the pulseaudio client api into its
310+// own namespace and make sure that only managed types
311+// can be passed to calls to pulseaudio. In addition,
312+// we add guards to the function calls to ensure that
313+// they are conly called on the correct thread.
314+namespace pa
315+{
316+typedef std::shared_ptr<pa_threaded_mainloop> ThreadedMainLoopPtr;
317+ThreadedMainLoopPtr make_threaded_main_loop()
318+{
319+ return ThreadedMainLoopPtr
320+ {
321+ pa_threaded_mainloop_new(),
322+ [](pa_threaded_mainloop* ml)
323+ {
324+ pa_threaded_mainloop_stop(ml);
325+ pa_threaded_mainloop_free(ml);
326+ }
327+ };
328+}
329+
330+void start_main_loop(ThreadedMainLoopPtr ml)
331+{
332+ pa_threaded_mainloop_start(ml.get());
333+}
334+
335+typedef std::shared_ptr<pa_context> ContextPtr;
336+ContextPtr make_context(ThreadedMainLoopPtr main_loop)
337+{
338+ return ContextPtr
339+ {
340+ pa_context_new(pa_threaded_mainloop_get_api(main_loop.get()), "MediaHubPulseContext"),
341+ pa_context_unref
342+ };
343+}
344+
345+void set_state_callback(ContextPtr ctxt, pa_context_notify_cb_t cb, void* cookie)
346+{
347+ pa_context_set_state_callback(ctxt.get(), cb, cookie);
348+}
349+
350+void set_subscribe_callback(ContextPtr ctxt, pa_context_subscribe_cb_t cb, void* cookie)
351+{
352+ pa_context_set_subscribe_callback(ctxt.get(), cb, cookie);
353+}
354+
355+void throw_if_not_on_main_loop(ThreadedMainLoopPtr ml)
356+{
357+ if (not pa_threaded_mainloop_in_thread(ml.get())) throw std::logic_error
358+ {
359+ "Attempted to call into a pulseaudio object from another"
360+ "thread than the pulseaudio mainloop thread."
361+ };
362+}
363+
364+void throw_if_not_connected(ContextPtr ctxt)
365+{
366+ if (pa_context_get_state(ctxt.get()) != PA_CONTEXT_READY ) throw std::logic_error
367+ {
368+ "Attempted to issue a call against pulseaudio via a non-connected context."
369+ };
370+}
371+
372+void get_server_info_async(ContextPtr ctxt, ThreadedMainLoopPtr ml, pa_server_info_cb_t cb, void* cookie)
373+{
374+ throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
375+ pa_operation_unref(pa_context_get_server_info(ctxt.get(), cb, cookie));
376+}
377+
378+void subscribe_to_events(ContextPtr ctxt, ThreadedMainLoopPtr ml, pa_subscription_mask mask)
379+{
380+ throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
381+ pa_operation_unref(pa_context_subscribe(ctxt.get(), mask, nullptr, nullptr));
382+}
383+
384+void get_index_of_sink_by_name_async(ContextPtr ctxt, ThreadedMainLoopPtr ml, const std::string& name, pa_sink_info_cb_t cb, void* cookie)
385+{
386+ throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
387+ pa_operation_unref(pa_context_get_sink_info_by_name(ctxt.get(), name.c_str(), cb, cookie));
388+}
389+
390+void get_sink_info_by_index_async(ContextPtr ctxt, ThreadedMainLoopPtr ml, std::int32_t index, pa_sink_info_cb_t cb, void* cookie)
391+{
392+ throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
393+ pa_operation_unref(pa_context_get_sink_info_by_index(ctxt.get(), index, cb, cookie));
394+}
395+
396+void connect_async(ContextPtr ctxt)
397+{
398+ pa_context_connect(ctxt.get(), nullptr, static_cast<pa_context_flags_t>(PA_CONTEXT_NOAUTOSPAWN | PA_CONTEXT_NOFAIL), nullptr);
399+}
400+
401+bool is_port_available_on_sink(const pa_sink_info* info, const std::regex& port_pattern)
402+{
403+ if (not info)
404+ return false;
405+
406+ for (std::uint32_t i = 0; i < info->n_ports; i++)
407+ {
408+ if (info->ports[i]->available == PA_PORT_AVAILABLE_NO ||
409+ info->ports[i]->available == PA_PORT_AVAILABLE_UNKNOWN)
410+ continue;
411+
412+ if (std::regex_match(std::string{info->ports[i]->name}, port_pattern))
413+ return true;
414+ }
415+
416+ return false;
417+}
418+}
419+}
420+
421+struct audio::PulseAudioOutputObserver::Private
422+{
423+ static void context_notification_cb(pa_context* ctxt, void* cookie)
424+ {
425+ if (auto thiz = static_cast<Private*>(cookie))
426+ {
427+ // Better safe than sorry: Check if we got signaled for the
428+ // context we are actually interested in.
429+ if (thiz->context.get() != ctxt)
430+ return;
431+
432+ switch (pa_context_get_state(ctxt))
433+ {
434+ case PA_CONTEXT_READY:
435+ thiz->on_context_ready();
436+ break;
437+ case PA_CONTEXT_FAILED:
438+ thiz->on_context_failed();
439+ break;
440+ default:
441+ break;
442+ }
443+ }
444+ }
445+
446+ static void context_subscription_cb(pa_context* ctxt, pa_subscription_event_type_t ev, uint32_t idx, void* cookie)
447+ {
448+ (void) idx;
449+
450+ if (auto thiz = static_cast<Private*>(cookie))
451+ {
452+ // Better safe than sorry: Check if we got signaled for the
453+ // context we are actually interested in.
454+ if (thiz->context.get() != ctxt)
455+ return;
456+
457+ if ((ev & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
458+ thiz->on_sink_event_with_index(idx);
459+ }
460+ }
461+
462+ static void query_for_active_sink_finished(pa_context* ctxt, const pa_sink_info* si, int eol, void* cookie)
463+ {
464+ if (eol)
465+ return;
466+
467+ if (auto thiz = static_cast<Private*>(cookie))
468+ {
469+ // Better safe than sorry: Check if we got signaled for the
470+ // context we are actually interested in.
471+ if (thiz->context.get() != ctxt)
472+ return;
473+
474+ thiz->on_query_for_active_sink_finished(si);
475+ }
476+ }
477+
478+ static void query_for_primary_sink_finished(pa_context* ctxt, const pa_sink_info* si, int eol, void* cookie)
479+ {
480+ if (eol)
481+ return;
482+
483+ if (auto thiz = static_cast<Private*>(cookie))
484+ {
485+ // Better safe than sorry: Check if we got signaled for the
486+ // context we are actually interested in.
487+ if (thiz->context.get() != ctxt)
488+ return;
489+
490+ thiz->on_query_for_primary_sink_finished(si);
491+ }
492+ }
493+
494+ static void query_for_server_info_finished(pa_context* ctxt, const pa_server_info* si, void* cookie)
495+ {
496+ if (not si)
497+ return;
498+
499+ if (auto thiz = static_cast<Private*>(cookie))
500+ {
501+ // Better safe than sorry: Check if we got signaled for the
502+ // context we are actually interested in.
503+ if (thiz->context.get() != ctxt)
504+ return;
505+
506+ thiz->on_query_for_server_info_finished(si);
507+ }
508+ }
509+
510+ Private(const audio::PulseAudioOutputObserver::Configuration& config)
511+ : config(config),
512+ main_loop{pa::make_threaded_main_loop()},
513+ context{pa::make_context(main_loop)},
514+ primary_sink_index(-1),
515+ active_sink(std::make_tuple(-1, ""))
516+ {
517+ for (const auto& pattern : config.output_port_patterns)
518+ {
519+ outputs.emplace_back(pattern, core::Property<media::audio::OutputState>{media::audio::OutputState::Speaker});
520+ std::get<1>(outputs.back()) | properties.external_output_state;
521+ std::get<1>(outputs.back()).changed().connect([](media::audio::OutputState state)
522+ {
523+ std::cout << "Connection state for port changed to: " << state << std::endl;
524+ });
525+ }
526+
527+ pa::set_state_callback(context, Private::context_notification_cb, this);
528+ pa::set_subscribe_callback(context, Private::context_subscription_cb, this);
529+
530+ pa::connect_async(context);
531+ pa::start_main_loop(main_loop);
532+ }
533+
534+ // The connection attempt has been successful and we are connected
535+ // to pulseaudio now.
536+ void on_context_ready()
537+ {
538+ config.reporter->connected_to_pulse_audio();
539+
540+ pa::subscribe_to_events(context, main_loop, PA_SUBSCRIPTION_MASK_SINK);
541+
542+ if (config.sink == "query.from.server")
543+ {
544+ pa::get_server_info_async(context, main_loop, Private::query_for_server_info_finished, this);
545+ }
546+ else
547+ {
548+ properties.sink = config.sink;
549+ // Get primary sink index (default)
550+ pa::get_index_of_sink_by_name_async(context, main_loop, config.sink, Private::query_for_primary_sink_finished, this);
551+ // Update active sink (could be == default)
552+ pa::get_server_info_async(context, main_loop, Private::query_for_server_info_finished, this);
553+ }
554+ }
555+
556+ // Either a connection attempt failed, or an existing connection
557+ // was unexpectedly terminated.
558+ void on_context_failed()
559+ {
560+ pa::connect_async(context);
561+ }
562+
563+ // Something changed on the sink with index idx.
564+ void on_sink_event_with_index(std::int32_t index)
565+ {
566+ config.reporter->sink_event_with_index(index);
567+
568+ // Update server info (active sink)
569+ pa::get_server_info_async(context, main_loop, Private::query_for_server_info_finished, this);
570+
571+ }
572+
573+ void on_query_for_active_sink_finished(const pa_sink_info* info)
574+ {
575+ // Update active sink if a change is registered.
576+ if (std::get<0>(active_sink) != info->index)
577+ {
578+ std::get<0>(active_sink) = info->index;
579+ std::get<1>(active_sink) = info->name;
580+ if (info->index != static_cast<std::uint32_t>(primary_sink_index))
581+ for (auto& element : outputs)
582+ std::get<1>(element) = audio::OutputState::External;
583+ }
584+ }
585+
586+ // Query for primary sink finished.
587+ void on_query_for_primary_sink_finished(const pa_sink_info* info)
588+ {
589+ for (auto& element : outputs)
590+ {
591+ // Only issue state change if the change happened on the active index.
592+ if (std::get<0>(active_sink) != info->index)
593+ continue;
594+
595+ std::cout << "Checking if port is available " << " -> " << std::boolalpha << pa::is_port_available_on_sink(info, std::get<0>(element)) << std::endl;
596+ bool available = pa::is_port_available_on_sink(info, std::get<0>(element));
597+
598+ if (available)
599+ {
600+ std::get<1>(element) = audio::OutputState::Earpiece;
601+ continue;
602+ }
603+
604+ audio::OutputState state;
605+ if (info->index == primary_sink_index)
606+ state = audio::OutputState::Speaker;
607+ else
608+ state = audio::OutputState::External;
609+
610+ std::get<1>(element) = state;
611+ }
612+
613+ std::set<Reporter::Port> known_ports;
614+ for (std::uint32_t i = 0; i < info->n_ports; i++)
615+ {
616+ bool is_monitored = false;
617+
618+ for (auto& element : outputs)
619+ is_monitored |= std::regex_match(info->ports[i]->name, std::get<0>(element));
620+
621+ known_ports.insert(Reporter::Port
622+ {
623+ info->ports[i]->name,
624+ info->ports[i]->description,
625+ info->ports[i]->available == PA_PORT_AVAILABLE_YES,
626+ is_monitored
627+ });
628+ }
629+
630+ properties.known_ports = known_ports;
631+
632+ // Initialize sink of primary index (onboard)
633+ if (primary_sink_index == -1)
634+ primary_sink_index = info->index;
635+
636+ config.reporter->query_for_sink_info_finished(info->name, info->index, known_ports);
637+ }
638+
639+ void on_query_for_server_info_finished(const pa_server_info* info)
640+ {
641+ // We bail out if we could not determine the default sink name.
642+ // In this case, we are not able to carry out audio output observation.
643+ if (not info->default_sink_name)
644+ {
645+ config.reporter->query_for_default_sink_failed();
646+ return;
647+ }
648+
649+ // Update active sink
650+ if (info->default_sink_name != std::get<1>(active_sink))
651+ pa::get_index_of_sink_by_name_async(context, main_loop, info->default_sink_name, Private::query_for_active_sink_finished, this);
652+
653+ // Update wired output for primary sink (onboard)
654+ pa::get_sink_info_by_index_async(context, main_loop, primary_sink_index, Private::query_for_primary_sink_finished, this);
655+
656+ if (properties.sink.get() != config.sink)
657+ {
658+ config.reporter->query_for_default_sink_finished(info->default_sink_name);
659+ properties.sink = config.sink = info->default_sink_name;
660+ pa::get_index_of_sink_by_name_async(context, main_loop, config.sink, Private::query_for_primary_sink_finished, this);
661+ }
662+ }
663+
664+ PulseAudioOutputObserver::Configuration config;
665+ pa::ThreadedMainLoopPtr main_loop;
666+ pa::ContextPtr context;
667+ std::int32_t primary_sink_index;
668+ std::tuple<uint32_t, std::string> active_sink;
669+ std::vector<std::tuple<std::regex, core::Property<media::audio::OutputState>>> outputs;
670+
671+ struct
672+ {
673+ core::Property<std::string> sink;
674+ core::Property<std::set<audio::PulseAudioOutputObserver::Reporter::Port>> known_ports;
675+ core::Property<audio::OutputState> external_output_state{audio::OutputState::Speaker};
676+ } properties;
677+};
678+
679+bool audio::PulseAudioOutputObserver::Reporter::Port::operator==(const audio::PulseAudioOutputObserver::Reporter::Port& rhs) const
680+{
681+ return name == rhs.name;
682+}
683+
684+bool audio::PulseAudioOutputObserver::Reporter::Port::operator<(const audio::PulseAudioOutputObserver::Reporter::Port& rhs) const
685+{
686+ return name < rhs.name;
687+}
688+
689+audio::PulseAudioOutputObserver::Reporter::~Reporter()
690+{
691+}
692+
693+void audio::PulseAudioOutputObserver::Reporter::connected_to_pulse_audio()
694+{
695+}
696+
697+void audio::PulseAudioOutputObserver::Reporter::query_for_default_sink_failed()
698+{
699+}
700+
701+void audio::PulseAudioOutputObserver::Reporter::query_for_default_sink_finished(const std::string&)
702+{
703+}
704+
705+void audio::PulseAudioOutputObserver::Reporter::query_for_sink_info_finished(const std::string&, std::uint32_t, const std::set<Port>&)
706+{
707+}
708+
709+void audio::PulseAudioOutputObserver::Reporter::sink_event_with_index(std::uint32_t)
710+{
711+}
712+
713+// Constructs a new instance, or throws std::runtime_error
714+// if connection to pulseaudio fails.
715+audio::PulseAudioOutputObserver::PulseAudioOutputObserver(const Configuration& config) : d{new Private{config}}
716+{
717+ if (not d->config.reporter) throw std::runtime_error
718+ {
719+ "PulseAudioOutputObserver: Cannot construct for invalid reporter instance."
720+ };
721+}
722+
723+// We provide the name of the sink we are connecting to as a
724+// getable/observable property. This is specifically meant for
725+// consumption by test code.
726+const core::Property<std::string>& audio::PulseAudioOutputObserver::sink() const
727+{
728+ return d->properties.sink;
729+}
730+
731+// The set of ports that have been identified on the configured sink.
732+// Specifically meant for consumption by test code.
733+const core::Property<std::set<audio::PulseAudioOutputObserver::Reporter::Port>>& audio::PulseAudioOutputObserver::known_ports() const
734+{
735+ return d->properties.known_ports;
736+}
737+
738+// Getable/observable property holding the state of external outputs.
739+const core::Property<audio::OutputState>& audio::PulseAudioOutputObserver::external_output_state() const
740+{
741+ return d->properties.external_output_state;
742+}
743
744=== added file 'src/core/media/audio/pulse_audio_output_observer.h'
745--- src/core/media/audio/pulse_audio_output_observer.h 1970-01-01 00:00:00 +0000
746+++ src/core/media/audio/pulse_audio_output_observer.h 2015-03-19 00:04:25 +0000
747@@ -0,0 +1,128 @@
748+/*
749+ * Copyright © 2014 Canonical Ltd.
750+ *
751+ * This program is free software: you can redistribute it and/or modify it
752+ * under the terms of the GNU Lesser General Public License version 3,
753+ * as published by the Free Software Foundation.
754+ *
755+ * This program is distributed in the hope that it will be useful,
756+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
757+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
758+ * GNU Lesser General Public License for more details.
759+ *
760+ * You should have received a copy of the GNU Lesser General Public License
761+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
762+ *
763+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
764+ * Ricardo Mendoza <ricardo.mendoza@canonical.com>
765+ */
766+#ifndef CORE_UBUNTU_MEDIA_AUDIO_PULSE_AUDIO_OUTPUT_OBSERVER_H_
767+#define CORE_UBUNTU_MEDIA_AUDIO_PULSE_AUDIO_OUTPUT_OBSERVER_H_
768+
769+#include <core/media/audio/output_observer.h>
770+
771+#include <iosfwd>
772+#include <memory>
773+#include <regex>
774+
775+namespace core
776+{
777+namespace ubuntu
778+{
779+namespace media
780+{
781+namespace audio
782+{
783+// Implements the audio::OutputObserver interface
784+// relying on pulse to query the connected ports
785+// of the primary card of the system.
786+class PulseAudioOutputObserver : public OutputObserver
787+{
788+public:
789+ // Save us some typing.
790+ typedef std::shared_ptr<PulseAudioOutputObserver> Ptr;
791+
792+ // Reporter is responsible for surfacing events from the implementation
793+ // that help in resolving/tracking down issues. Default implementation is empty.
794+ struct Reporter
795+ {
796+ // To save us some typing.
797+ typedef std::shared_ptr<Reporter> Ptr;
798+
799+ // Simple type to help in reporting.
800+ struct Port
801+ {
802+ // Returns true iff the name of both ports are equal.
803+ bool operator==(const Port& rhs) const;
804+ // Returns true iff the name of the ports differ.
805+ bool operator<(const Port& rhs) const;
806+
807+ std::string name; // The name of the port.
808+ std::string description; // Human-readable description of the port.
809+ bool is_available; // True if the port is available.
810+ bool is_monitored; // True if the port is monitored by the observer.
811+ };
812+
813+ virtual ~Reporter();
814+ // connected_to_pulse_audio is called when a connection with pulse has been established.
815+ virtual void connected_to_pulse_audio();
816+ // query_for_default_sink_failed is called when no default sink was returned.
817+ virtual void query_for_default_sink_failed();
818+ // query_for_default_sink_finished is called when the default sink query against pulse
819+ // has finished, reporting the name of the sink to observers.
820+ virtual void query_for_default_sink_finished(const std::string& sink_name);
821+ // query_for_sink_info_finished is called when a query for information about a specific sink
822+ // has finished, reporting the name, index of the sink as well as the set of ports known to the sink.
823+ virtual void query_for_sink_info_finished(const std::string& name, std::uint32_t index, const std::set<Port>& known_ports);
824+ // sink_event_with_index is called when something happened on a sink, reporing the index of the
825+ // sink.
826+ virtual void sink_event_with_index(std::uint32_t index);
827+ };
828+
829+ // Construction time arguments go here
830+ struct Configuration
831+ {
832+ // Name of the sink that we should consider.
833+ std::string sink
834+ {
835+ // A special value that requests the implementation to
836+ // query pulseaudio for the default configured sink.
837+ "query.from.server"
838+ };
839+ // Output port name patterns that should be observed on the configured sink.
840+ // All patterns have to be valid regular expressions.
841+ std::vector<std::regex> output_port_patterns
842+ {
843+ // Any port is considered with this special value.
844+ std::regex{".+"}
845+ };
846+ // The Reporter instance that the implementation reports
847+ // events to. Must not be null.
848+ Reporter::Ptr reporter{std::make_shared<Reporter>()};
849+ };
850+
851+ // Constructs a new instance, throws:
852+ // * std::runtime_error if connection to pulseaudio fails.
853+ // * std::runtime_error if reporter instance is null.
854+ PulseAudioOutputObserver(const Configuration&);
855+
856+ // We provide the name of the sink we are connecting to as a
857+ // getable/observable property. This is specifically meant for
858+ // consumption by test code.
859+ const core::Property<std::string>& sink() const;
860+ // The set of ports that have been identified on the configured sink.
861+ // Specifically meant for consumption by test code.
862+ const core::Property<std::set<Reporter::Port>>& known_ports() const;
863+ // Getable/observable property holding the state of external outputs.
864+ const core::Property<OutputState>& external_output_state() const override;
865+
866+private:
867+ struct Private;
868+ std::shared_ptr<Private> d;
869+};
870+}
871+}
872+}
873+}
874+
875+#endif // CORE_UBUNTU_MEDIA_AUDIO_PULSE_AUDIO_OUTPUT_OBSERVER_H_
876
877=== modified file 'src/core/media/gstreamer/playbin.cpp'
878--- src/core/media/gstreamer/playbin.cpp 2015-03-19 00:04:24 +0000
879+++ src/core/media/gstreamer/playbin.cpp 2015-03-19 00:04:25 +0000
880@@ -406,6 +406,9 @@
881 };
882
883 auto ret = gst_element_set_state(pipeline, new_state);
884+
885+ std::cout << __PRETTY_FUNCTION__ << ": requested state change." << std::endl;
886+
887 bool result = false; GstState current, pending;
888 switch(ret)
889 {
890@@ -419,7 +422,7 @@
891 pipeline,
892 &current,
893 &pending,
894- state_change_timeout.count());
895+ state_change_timeout.count());
896 break;
897 }
898
899
900=== modified file 'src/core/media/service_implementation.cpp'
901--- src/core/media/service_implementation.cpp 2015-03-19 00:04:24 +0000
902+++ src/core/media/service_implementation.cpp 2015-03-19 00:04:25 +0000
903@@ -22,6 +22,7 @@
904
905 #include "service_implementation.h"
906
907+#include "audio/output_observer.h"
908 #include "client_death_observer.h"
909 #include "call-monitor/call_monitor.h"
910 #include "player_configuration.h"
911@@ -57,349 +58,10 @@
912 display_state_lock(power_state_controller->display_state_lock()),
913 client_death_observer(media::platform_default_client_death_observer()),
914 recorder_observer(media::make_platform_default_recorder_observer()),
915- pulse_mainloop_api(nullptr),
916- pulse_context(nullptr),
917- headphones_connected(false),
918- a2dp_connected(false),
919- primary_idx(-1),
920+ audio_output_observer(media::audio::make_platform_default_output_observer()),
921+ audio_output_state(media::audio::OutputState::Speaker),
922 call_monitor(new CallMonitor)
923 {
924- // Spawn pulse watchdog
925- pulse_mainloop = nullptr;
926- pulse_worker = std::move(std::thread([this]()
927- {
928- std::unique_lock<std::mutex> lk(pulse_mutex);
929- pcv.wait(lk,
930- [this]{
931- if (pulse_mainloop != nullptr || pulse_context != nullptr)
932- {
933- // We come from instance death, destroy and create.
934- if (pulse_context != nullptr)
935- {
936- pa_threaded_mainloop_lock(pulse_mainloop);
937- pa_operation *o;
938- o = pa_context_drain(pulse_context,
939- [](pa_context *context, void *userdata)
940- {
941- (void) context;
942-
943- Private *p = reinterpret_cast<Private*>(userdata);
944- pa_threaded_mainloop_signal(p->mainloop(), 0);
945- }, this);
946-
947- if (o)
948- {
949- while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
950- pa_threaded_mainloop_wait(pulse_mainloop);
951-
952- pa_operation_unref(o);
953- }
954-
955- pa_context_set_state_callback(pulse_context, NULL, NULL);
956- pa_context_set_subscribe_callback(pulse_context, NULL, NULL);
957- pa_context_disconnect(pulse_context);
958- pa_context_unref(pulse_context);
959- pulse_context = nullptr;
960- pa_threaded_mainloop_unlock(pulse_mainloop);
961- }
962- }
963-
964- if (pulse_mainloop == nullptr)
965- {
966- pulse_mainloop = pa_threaded_mainloop_new();
967-
968- if (pa_threaded_mainloop_start(pulse_mainloop) != 0)
969- {
970- std::cerr << "Unable to start pulseaudio mainloop, audio output detection will not function" << std::endl;
971- pa_threaded_mainloop_free(pulse_mainloop);
972- pulse_mainloop = nullptr;
973- }
974- }
975-
976- do {
977- create_pulse_context();
978- } while (pulse_context == nullptr);
979-
980- // Wait for next instance death.
981- return false;
982- });
983- }));
984-
985- recorder_observer->recording_state().changed().connect([this](media::RecordingState state)
986- {
987- media_recording_state_changed(state);
988- });
989- }
990-
991- ~Private()
992- {
993- release_pulse_context();
994-
995- if (pulse_mainloop != nullptr)
996- {
997- pa_threaded_mainloop_stop(pulse_mainloop);
998- pa_threaded_mainloop_free(pulse_mainloop);
999- pulse_mainloop = nullptr;
1000- }
1001-
1002- if (pulse_worker.joinable())
1003- pulse_worker.join();
1004- }
1005-
1006- void media_recording_state_changed(media::RecordingState state)
1007- {
1008- if (state == media::RecordingState::started)
1009- {
1010- display_state_lock->request_acquire(media::power::DisplayState::on);
1011- pause_playback();
1012- }
1013- else if (state == media::RecordingState::stopped)
1014- {
1015- display_state_lock->request_release(media::power::DisplayState::on);
1016- }
1017- }
1018-
1019- pa_threaded_mainloop *mainloop()
1020- {
1021- return pulse_mainloop;
1022- }
1023-
1024- bool is_port_available(pa_card_port_info **ports, uint32_t n_ports, const char *name)
1025- {
1026- bool ret = false;
1027-
1028- if (ports != nullptr && n_ports > 0 && name != nullptr)
1029- {
1030- for (uint32_t i=0; i<n_ports; i++)
1031- {
1032- if (strstr(ports[i]->name, name) != nullptr && ports[i]->available != PA_PORT_AVAILABLE_NO)
1033- {
1034- ret = true;
1035- break;
1036- }
1037- }
1038- }
1039-
1040- return ret;
1041- }
1042-
1043- void update_wired_output()
1044- {
1045- const pa_operation *o = pa_context_get_card_info_by_index(pulse_context, primary_idx,
1046- [](pa_context *context, const pa_card_info *info, int eol, void *userdata)
1047- {
1048- (void) context;
1049- (void) eol;
1050-
1051- if (info == nullptr || userdata == nullptr)
1052- return;
1053-
1054- Private *p = reinterpret_cast<Private*>(userdata);
1055- if (p->is_port_available(info->ports, info->n_ports, "output-wired"))
1056- {
1057- if (!p->headphones_connected)
1058- std::cout << "Wired headphones connected" << std::endl;
1059- p->headphones_connected = true;
1060- }
1061- else if (p->headphones_connected == true)
1062- {
1063- std::cout << "Wired headphones disconnected" << std::endl;
1064- p->headphones_connected = false;
1065- p->pause_playback_if_necessary(std::get<0>(p->active_sink));
1066- }
1067- }, this);
1068- (void) o;
1069- }
1070-
1071- void pause_playback_if_necessary(int index)
1072- {
1073- // Catch uninitialized case (active index == -1)
1074- if (std::get<0>(active_sink) == -1)
1075- return;
1076-
1077- if (headphones_connected)
1078- return;
1079-
1080- // No headphones/fallback on primary sink? Pause.
1081- if (index == primary_idx)
1082- pause_playback();
1083- }
1084-
1085- void set_active_sink(const char *name)
1086- {
1087- const pa_operation *o = pa_context_get_sink_info_by_name(pulse_context, name,
1088- [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
1089- {
1090- (void) context;
1091-
1092- if (eol)
1093- return;
1094-
1095- Private *p = reinterpret_cast<Private*>(userdata);
1096- std::tuple<uint32_t, uint32_t, std::string> new_sink(std::make_tuple(i->index, i->card, i->name));
1097-
1098- printf("pulsesink: active_sink=('%s',%d,%d) -> ('%s',%d,%d)\n",
1099- std::get<2>(p->active_sink).c_str(), std::get<0>(p->active_sink),
1100- std::get<1>(p->active_sink), i->name, i->index, i->card);
1101-
1102- p->pause_playback_if_necessary(i->index);
1103- p->active_sink = new_sink;
1104- }, this);
1105-
1106- (void) o;
1107- }
1108-
1109- void update_active_sink()
1110- {
1111- const pa_operation *o = pa_context_get_server_info(pulse_context,
1112- [](pa_context *context, const pa_server_info *i, void *userdata)
1113- {
1114- (void) context;
1115-
1116- Private *p = reinterpret_cast<Private*>(userdata);
1117- if (i->default_sink_name != std::get<2>(p->active_sink))
1118- p->set_active_sink(i->default_sink_name);
1119- p->update_wired_output();
1120- }, this);
1121-
1122- (void) o;
1123- }
1124-
1125- void create_pulse_context()
1126- {
1127- if (pulse_context != nullptr)
1128- return;
1129-
1130- active_sink = std::make_tuple(-1, -1, "");
1131-
1132- bool keep_going = true, ok = true;
1133-
1134- pulse_mainloop_api = pa_threaded_mainloop_get_api(pulse_mainloop);
1135- pa_threaded_mainloop_lock(pulse_mainloop);
1136-
1137- pulse_context = pa_context_new(pulse_mainloop_api, "MediaHubPulseContext");
1138- pa_context_set_state_callback(pulse_context,
1139- [](pa_context *context, void *userdata)
1140- {
1141- (void) context;
1142- Private *p = reinterpret_cast<Private*>(userdata);
1143- // Signals the pa_threaded_mainloop_wait below to proceed
1144- pa_threaded_mainloop_signal(p->mainloop(), 0);
1145- }, this);
1146-
1147- if (pulse_context == nullptr)
1148- {
1149- std::cerr << "Unable to create new pulseaudio context" << std::endl;
1150- pa_threaded_mainloop_unlock(pulse_mainloop);
1151- return;
1152- }
1153-
1154- pa_context_connect(pulse_context, nullptr, pa_context_flags_t((int) PA_CONTEXT_NOAUTOSPAWN | (int) PA_CONTEXT_NOFAIL), nullptr);
1155- pa_threaded_mainloop_wait(pulse_mainloop);
1156-
1157- while (keep_going)
1158- {
1159- switch (pa_context_get_state(pulse_context))
1160- {
1161- case PA_CONTEXT_CONNECTING: // Wait for service to be available
1162- case PA_CONTEXT_AUTHORIZING:
1163- case PA_CONTEXT_SETTING_NAME:
1164- break;
1165-
1166- case PA_CONTEXT_READY:
1167- std::cout << "Pulseaudio connection established." << std::endl;
1168- keep_going = false;
1169- break;
1170-
1171- case PA_CONTEXT_FAILED:
1172- case PA_CONTEXT_TERMINATED:
1173- keep_going = false;
1174- ok = false;
1175- break;
1176-
1177- default:
1178- std::cerr << "Pulseaudio connection failure: " << pa_strerror(pa_context_errno(pulse_context));
1179- keep_going = false;
1180- ok = false;
1181- }
1182-
1183- if (keep_going)
1184- pa_threaded_mainloop_wait(pulse_mainloop);
1185- }
1186-
1187- if (ok)
1188- {
1189- pa_context_set_state_callback(pulse_context,
1190- [](pa_context *context, void *userdata)
1191- {
1192- (void) context;
1193- (void) userdata;
1194- Private *p = reinterpret_cast<Private*>(userdata);
1195- std::unique_lock<std::mutex> lk(p->pulse_mutex);
1196- switch (pa_context_get_state(context))
1197- {
1198- case PA_CONTEXT_FAILED:
1199- case PA_CONTEXT_TERMINATED:
1200- p->pcv.notify_all();
1201- break;
1202- default:
1203- break;
1204- }
1205- }, this);
1206-
1207- //FIXME: Get index for "sink.primary", the default onboard card on Touch.
1208- pa_context_get_sink_info_by_name(pulse_context, "sink.primary",
1209- [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
1210- {
1211- (void) context;
1212-
1213- if (eol)
1214- return;
1215-
1216- Private *p = reinterpret_cast<Private*>(userdata);
1217- p->primary_idx = i->index;
1218- p->update_wired_output();
1219- }, this);
1220-
1221- update_active_sink();
1222-
1223- pa_context_set_subscribe_callback(pulse_context,
1224- [](pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
1225- {
1226- (void) context;
1227- (void) idx;
1228-
1229- if (userdata == nullptr)
1230- return;
1231-
1232- Private *p = reinterpret_cast<Private*>(userdata);
1233- if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
1234- {
1235- p->update_active_sink();
1236- }
1237- }, this);
1238- pa_context_subscribe(pulse_context, PA_SUBSCRIPTION_MASK_SINK, nullptr, this);
1239- }
1240- else
1241- {
1242- std::cerr << "Connection to pulseaudio failed or was dropped." << std::endl;
1243- pa_context_unref(pulse_context);
1244- pulse_context = nullptr;
1245- }
1246-
1247- pa_threaded_mainloop_unlock(pulse_mainloop);
1248- }
1249-
1250- void release_pulse_context()
1251- {
1252- if (pulse_context != nullptr)
1253- {
1254- pa_threaded_mainloop_lock(pulse_mainloop);
1255- pa_context_disconnect(pulse_context);
1256- pa_context_unref(pulse_context);
1257- pa_threaded_mainloop_unlock(pulse_mainloop);
1258- pulse_context = nullptr;
1259- }
1260 }
1261
1262 media::ServiceImplementation::Configuration configuration;
1263@@ -411,22 +73,9 @@
1264 media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;
1265 media::ClientDeathObserver::Ptr client_death_observer;
1266 media::RecorderObserver::Ptr recorder_observer;
1267- // Pulse-specific
1268- pa_mainloop_api *pulse_mainloop_api;
1269- pa_threaded_mainloop *pulse_mainloop;
1270- pa_context *pulse_context;
1271- std::thread pulse_worker;
1272- std::mutex pulse_mutex;
1273- std::condition_variable pcv;
1274- bool headphones_connected;
1275- bool a2dp_connected;
1276- std::tuple<int, int, std::string> active_sink;
1277- int primary_idx;
1278+ media::audio::OutputObserver::Ptr audio_output_observer;
1279+ media::audio::OutputState audio_output_state;
1280
1281- // Gets signaled when both the headphone jack is removed or an A2DP device is
1282- // disconnected and playback needs pausing. Also gets signaled when recording
1283- // begins.
1284- core::Signal<void> pause_playback;
1285 std::unique_ptr<CallMonitor> call_monitor;
1286 std::list<media::Player::PlayerKey> paused_sessions;
1287 };
1288@@ -456,10 +105,22 @@
1289 resume_multimedia_session();
1290 });
1291
1292- d->pause_playback.connect([this]()
1293+ d->audio_output_observer->external_output_state().changed().connect([this](audio::OutputState state)
1294 {
1295- std::cout << "Got pause_playback signal, pausing all multimedia sessions" << std::endl;
1296- pause_all_multimedia_sessions();
1297+ switch (state)
1298+ {
1299+ case audio::OutputState::Earpiece:
1300+ std::cout << "AudioOutputObserver reports that output is now Headphones/Headset." << std::endl;
1301+ break;
1302+ case audio::OutputState::Speaker:
1303+ std::cout << "AudioOutputObserver reports that output is now Speaker." << std::endl;
1304+ pause_all_multimedia_sessions();
1305+ break;
1306+ case audio::OutputState::External:
1307+ std::cout << "AudioOutputObserver reports that output is now External." << std::endl;
1308+ break;
1309+ }
1310+ d->audio_output_state = state;
1311 });
1312
1313 d->call_monitor->on_change([this](CallMonitor::State state) {
1314@@ -475,6 +136,19 @@
1315 break;
1316 }
1317 });
1318+
1319+ d->recorder_observer->recording_state().changed().connect([this](RecordingState state)
1320+ {
1321+ if (state == media::RecordingState::started)
1322+ {
1323+ d->display_state_lock->request_acquire(media::power::DisplayState::on);
1324+ pause_all_multimedia_sessions();
1325+ }
1326+ else if (state == media::RecordingState::stopped)
1327+ {
1328+ d->display_state_lock->request_release(media::power::DisplayState::on);
1329+ }
1330+ });
1331 }
1332
1333 media::ServiceImplementation::~ServiceImplementation()

Subscribers

People subscribed via source and target branches