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
=== modified file 'src/core/media/CMakeLists.txt'
--- src/core/media/CMakeLists.txt 2015-03-19 00:04:24 +0000
+++ src/core/media/CMakeLists.txt 2015-03-19 00:04:25 +0000
@@ -91,6 +91,10 @@
91 cover_art_resolver.cpp91 cover_art_resolver.cpp
92 engine.cpp92 engine.cpp
9393
94 audio/pulse_audio_output_observer.cpp
95 audio/ostream_reporter.cpp
96 audio/output_observer.cpp
97
94 power/battery_observer.cpp98 power/battery_observer.cpp
95 power/state_controller.cpp99 power/state_controller.cpp
96100
97101
=== added directory 'src/core/media/audio'
=== added file 'src/core/media/audio/ostream_reporter.cpp'
--- src/core/media/audio/ostream_reporter.cpp 1970-01-01 00:00:00 +0000
+++ src/core/media/audio/ostream_reporter.cpp 2015-03-19 00:04:25 +0000
@@ -0,0 +1,54 @@
1/*
2 * Copyright © 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 */
18
19#include <core/media/audio/ostream_reporter.h>
20
21namespace audio = core::ubuntu::media::audio;
22
23audio::OStreamReporter::OStreamReporter(std::ostream &out) : out{out}
24{
25}
26
27void audio::OStreamReporter::connected_to_pulse_audio()
28{
29 out << "Connection to PulseAudio has been successfully established." << std::endl;
30}
31
32void audio::OStreamReporter::query_for_default_sink_failed()
33{
34 out << "Query for default sink failed." << std::endl;
35}
36
37void audio::OStreamReporter::query_for_default_sink_finished(const std::string& sink_name)
38{
39 out << "Default PulseAudio sync has been identified: " << sink_name << std::endl;
40}
41
42void audio::OStreamReporter::query_for_sink_info_finished(const std::string& name, std::uint32_t index, const std::set<Port>& known_ports)
43{
44 out << "PulseAudio sink details for " << name << " with index " << index << " is available:" << std::endl;
45 for (const auto& port : known_ports)
46 {
47 if (port.is_monitored)
48 out << " " << port.description << ": " << std::boolalpha << port.is_available << "\n";
49 }
50}
51void audio::OStreamReporter::sink_event_with_index(std::uint32_t index)
52{
53 out << "PulseAudio event for sink with index " << index << " received." << std::endl;
54}
055
=== added file 'src/core/media/audio/ostream_reporter.h'
--- src/core/media/audio/ostream_reporter.h 1970-01-01 00:00:00 +0000
+++ src/core/media/audio/ostream_reporter.h 2015-03-19 00:04:25 +0000
@@ -0,0 +1,55 @@
1/*
2 * Copyright © 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 */
18#ifndef CORE_UBUNTU_MEDIA_AUDIO_OSTREAM_REPORTER_H_
19#define CORE_UBUNTU_MEDIA_AUDIO_OSTREAM_REPORTER_H_
20
21#include <core/media/audio/pulse_audio_output_observer.h>
22
23#include <iosfwd>
24
25namespace core
26{
27namespace ubuntu
28{
29namespace media
30{
31namespace audio
32{
33// A PulseAudioOutputObserver::Reporter implementation printing events to
34// the configured output stream.
35class OStreamReporter : public PulseAudioOutputObserver::Reporter
36{
37public:
38 // Constructs a new reporter instance, outputting events to the given stream.
39 OStreamReporter(std::ostream& out = std::cout);
40
41 void connected_to_pulse_audio() override;
42 void query_for_default_sink_failed() override;
43 void query_for_default_sink_finished(const std::string& sink_name) override;
44 void query_for_sink_info_finished(const std::string& name, std::uint32_t index, const std::set<Port>& known_ports) override;
45 void sink_event_with_index(std::uint32_t index) override;
46
47private:
48 std::ostream& out;
49};
50}
51}
52}
53}
54
55#endif // CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OSTREAM_REPORTER_H_
056
=== added file 'src/core/media/audio/output_observer.cpp'
--- src/core/media/audio/output_observer.cpp 1970-01-01 00:00:00 +0000
+++ src/core/media/audio/output_observer.cpp 2015-03-19 00:04:25 +0000
@@ -0,0 +1,50 @@
1/*
2 * Copyright © 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 */
18
19#include <core/media/audio/output_observer.h>
20
21#include <core/media/audio/pulse_audio_output_observer.h>
22#include <core/media/audio/ostream_reporter.h>
23
24#include <iostream>
25
26namespace audio = core::ubuntu::media::audio;
27
28std::ostream& audio::operator<<(std::ostream& out, audio::OutputState state)
29{
30 switch (state)
31 {
32 case audio::OutputState::Earpiece:
33 return out << "OutputState::Earpiece";
34 case audio::OutputState::Speaker:
35 return out << "OutputState::Speaker";
36 case audio::OutputState::External:
37 return out << "OutputState::External";
38 }
39
40 return out;
41}
42
43audio::OutputObserver::Ptr audio::make_platform_default_output_observer()
44{
45 audio::PulseAudioOutputObserver::Configuration config;
46 config.sink = "sink.primary";
47 config.output_port_patterns = {std::regex{"output-wired_head.*|output-a2dp_headphones"}};
48 config.reporter = std::make_shared<audio::OStreamReporter>();
49 return std::make_shared<audio::PulseAudioOutputObserver>(config);
50}
051
=== added file 'src/core/media/audio/output_observer.h'
--- src/core/media/audio/output_observer.h 1970-01-01 00:00:00 +0000
+++ src/core/media/audio/output_observer.h 2015-03-19 00:04:25 +0000
@@ -0,0 +1,76 @@
1/*
2 * Copyright © 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 */
18#ifndef CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OBSERVER_H_
19#define CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OBSERVER_H_
20
21#include <core/property.h>
22
23#include <iosfwd>
24#include <memory>
25
26namespace core
27{
28namespace ubuntu
29{
30namespace media
31{
32namespace audio
33{
34// All known states of an audio output.
35enum class OutputState
36{
37 // The output is via a private earpiece (i.e. headphones, headset)
38 Earpiece,
39 // The output is via the internal speaker.
40 Speaker,
41 // The output is via an external device (a2dp, etc)
42 External,
43};
44
45// Models observation of audio outputs of a device.
46// Right now, we are only interested in monitoring the
47// state of external outputs to react accordingly if
48// wired or bluetooth outputs are connected/disconnected.
49class OutputObserver
50{
51public:
52 // Save us some typing.
53 typedef std::shared_ptr<OutputObserver> Ptr;
54
55 virtual ~OutputObserver() = default;
56
57 // Getable/observable property holding the state of external outputs.
58 virtual const core::Property<OutputState>& external_output_state() const = 0;
59
60protected:
61 OutputObserver() = default;
62 OutputObserver(const OutputObserver&) = delete;
63 OutputObserver& operator=(const OutputObserver&) = delete;
64};
65
66// Pretty prints the given state to the given output stream.
67std::ostream& operator<<(std::ostream&, OutputState);
68
69// Creats a platform default instance for observing audio outputs.
70OutputObserver::Ptr make_platform_default_output_observer();
71}
72}
73}
74}
75
76#endif // CORE_UBUNTU_MEDIA_AUDIO_OUTPUT_OBSERVER_H_
077
=== added file 'src/core/media/audio/pulse_audio_output_observer.cpp'
--- src/core/media/audio/pulse_audio_output_observer.cpp 1970-01-01 00:00:00 +0000
+++ src/core/media/audio/pulse_audio_output_observer.cpp 2015-03-19 00:04:25 +0000
@@ -0,0 +1,467 @@
1/*
2 * Copyright © 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 * Ricardo Mendoza <ricardo.mendoza@canonical.com>
18 */
19
20#include <core/media/audio/pulse_audio_output_observer.h>
21
22#include <pulse/pulseaudio.h>
23
24#include <cstdint>
25
26#include <map>
27#include <regex>
28#include <string>
29
30namespace audio = core::ubuntu::media::audio;
31
32namespace
33{
34// We wrap calls to the pulseaudio client api into its
35// own namespace and make sure that only managed types
36// can be passed to calls to pulseaudio. In addition,
37// we add guards to the function calls to ensure that
38// they are conly called on the correct thread.
39namespace pa
40{
41typedef std::shared_ptr<pa_threaded_mainloop> ThreadedMainLoopPtr;
42ThreadedMainLoopPtr make_threaded_main_loop()
43{
44 return ThreadedMainLoopPtr
45 {
46 pa_threaded_mainloop_new(),
47 [](pa_threaded_mainloop* ml)
48 {
49 pa_threaded_mainloop_stop(ml);
50 pa_threaded_mainloop_free(ml);
51 }
52 };
53}
54
55void start_main_loop(ThreadedMainLoopPtr ml)
56{
57 pa_threaded_mainloop_start(ml.get());
58}
59
60typedef std::shared_ptr<pa_context> ContextPtr;
61ContextPtr make_context(ThreadedMainLoopPtr main_loop)
62{
63 return ContextPtr
64 {
65 pa_context_new(pa_threaded_mainloop_get_api(main_loop.get()), "MediaHubPulseContext"),
66 pa_context_unref
67 };
68}
69
70void set_state_callback(ContextPtr ctxt, pa_context_notify_cb_t cb, void* cookie)
71{
72 pa_context_set_state_callback(ctxt.get(), cb, cookie);
73}
74
75void set_subscribe_callback(ContextPtr ctxt, pa_context_subscribe_cb_t cb, void* cookie)
76{
77 pa_context_set_subscribe_callback(ctxt.get(), cb, cookie);
78}
79
80void throw_if_not_on_main_loop(ThreadedMainLoopPtr ml)
81{
82 if (not pa_threaded_mainloop_in_thread(ml.get())) throw std::logic_error
83 {
84 "Attempted to call into a pulseaudio object from another"
85 "thread than the pulseaudio mainloop thread."
86 };
87}
88
89void throw_if_not_connected(ContextPtr ctxt)
90{
91 if (pa_context_get_state(ctxt.get()) != PA_CONTEXT_READY ) throw std::logic_error
92 {
93 "Attempted to issue a call against pulseaudio via a non-connected context."
94 };
95}
96
97void get_server_info_async(ContextPtr ctxt, ThreadedMainLoopPtr ml, pa_server_info_cb_t cb, void* cookie)
98{
99 throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
100 pa_operation_unref(pa_context_get_server_info(ctxt.get(), cb, cookie));
101}
102
103void subscribe_to_events(ContextPtr ctxt, ThreadedMainLoopPtr ml, pa_subscription_mask mask)
104{
105 throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
106 pa_operation_unref(pa_context_subscribe(ctxt.get(), mask, nullptr, nullptr));
107}
108
109void get_index_of_sink_by_name_async(ContextPtr ctxt, ThreadedMainLoopPtr ml, const std::string& name, pa_sink_info_cb_t cb, void* cookie)
110{
111 throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
112 pa_operation_unref(pa_context_get_sink_info_by_name(ctxt.get(), name.c_str(), cb, cookie));
113}
114
115void get_sink_info_by_index_async(ContextPtr ctxt, ThreadedMainLoopPtr ml, std::int32_t index, pa_sink_info_cb_t cb, void* cookie)
116{
117 throw_if_not_on_main_loop(ml); throw_if_not_connected(ctxt);
118 pa_operation_unref(pa_context_get_sink_info_by_index(ctxt.get(), index, cb, cookie));
119}
120
121void connect_async(ContextPtr ctxt)
122{
123 pa_context_connect(ctxt.get(), nullptr, static_cast<pa_context_flags_t>(PA_CONTEXT_NOAUTOSPAWN | PA_CONTEXT_NOFAIL), nullptr);
124}
125
126bool is_port_available_on_sink(const pa_sink_info* info, const std::regex& port_pattern)
127{
128 if (not info)
129 return false;
130
131 for (std::uint32_t i = 0; i < info->n_ports; i++)
132 {
133 if (info->ports[i]->available == PA_PORT_AVAILABLE_NO ||
134 info->ports[i]->available == PA_PORT_AVAILABLE_UNKNOWN)
135 continue;
136
137 if (std::regex_match(std::string{info->ports[i]->name}, port_pattern))
138 return true;
139 }
140
141 return false;
142}
143}
144}
145
146struct audio::PulseAudioOutputObserver::Private
147{
148 static void context_notification_cb(pa_context* ctxt, void* cookie)
149 {
150 if (auto thiz = static_cast<Private*>(cookie))
151 {
152 // Better safe than sorry: Check if we got signaled for the
153 // context we are actually interested in.
154 if (thiz->context.get() != ctxt)
155 return;
156
157 switch (pa_context_get_state(ctxt))
158 {
159 case PA_CONTEXT_READY:
160 thiz->on_context_ready();
161 break;
162 case PA_CONTEXT_FAILED:
163 thiz->on_context_failed();
164 break;
165 default:
166 break;
167 }
168 }
169 }
170
171 static void context_subscription_cb(pa_context* ctxt, pa_subscription_event_type_t ev, uint32_t idx, void* cookie)
172 {
173 (void) idx;
174
175 if (auto thiz = static_cast<Private*>(cookie))
176 {
177 // Better safe than sorry: Check if we got signaled for the
178 // context we are actually interested in.
179 if (thiz->context.get() != ctxt)
180 return;
181
182 if ((ev & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
183 thiz->on_sink_event_with_index(idx);
184 }
185 }
186
187 static void query_for_active_sink_finished(pa_context* ctxt, const pa_sink_info* si, int eol, void* cookie)
188 {
189 if (eol)
190 return;
191
192 if (auto thiz = static_cast<Private*>(cookie))
193 {
194 // Better safe than sorry: Check if we got signaled for the
195 // context we are actually interested in.
196 if (thiz->context.get() != ctxt)
197 return;
198
199 thiz->on_query_for_active_sink_finished(si);
200 }
201 }
202
203 static void query_for_primary_sink_finished(pa_context* ctxt, const pa_sink_info* si, int eol, void* cookie)
204 {
205 if (eol)
206 return;
207
208 if (auto thiz = static_cast<Private*>(cookie))
209 {
210 // Better safe than sorry: Check if we got signaled for the
211 // context we are actually interested in.
212 if (thiz->context.get() != ctxt)
213 return;
214
215 thiz->on_query_for_primary_sink_finished(si);
216 }
217 }
218
219 static void query_for_server_info_finished(pa_context* ctxt, const pa_server_info* si, void* cookie)
220 {
221 if (not si)
222 return;
223
224 if (auto thiz = static_cast<Private*>(cookie))
225 {
226 // Better safe than sorry: Check if we got signaled for the
227 // context we are actually interested in.
228 if (thiz->context.get() != ctxt)
229 return;
230
231 thiz->on_query_for_server_info_finished(si);
232 }
233 }
234
235 Private(const audio::PulseAudioOutputObserver::Configuration& config)
236 : config(config),
237 main_loop{pa::make_threaded_main_loop()},
238 context{pa::make_context(main_loop)},
239 primary_sink_index(-1),
240 active_sink(std::make_tuple(-1, ""))
241 {
242 for (const auto& pattern : config.output_port_patterns)
243 {
244 outputs.emplace_back(pattern, core::Property<media::audio::OutputState>{media::audio::OutputState::Speaker});
245 std::get<1>(outputs.back()) | properties.external_output_state;
246 std::get<1>(outputs.back()).changed().connect([](media::audio::OutputState state)
247 {
248 std::cout << "Connection state for port changed to: " << state << std::endl;
249 });
250 }
251
252 pa::set_state_callback(context, Private::context_notification_cb, this);
253 pa::set_subscribe_callback(context, Private::context_subscription_cb, this);
254
255 pa::connect_async(context);
256 pa::start_main_loop(main_loop);
257 }
258
259 // The connection attempt has been successful and we are connected
260 // to pulseaudio now.
261 void on_context_ready()
262 {
263 config.reporter->connected_to_pulse_audio();
264
265 pa::subscribe_to_events(context, main_loop, PA_SUBSCRIPTION_MASK_SINK);
266
267 if (config.sink == "query.from.server")
268 {
269 pa::get_server_info_async(context, main_loop, Private::query_for_server_info_finished, this);
270 }
271 else
272 {
273 properties.sink = config.sink;
274 // Get primary sink index (default)
275 pa::get_index_of_sink_by_name_async(context, main_loop, config.sink, Private::query_for_primary_sink_finished, this);
276 // Update active sink (could be == default)
277 pa::get_server_info_async(context, main_loop, Private::query_for_server_info_finished, this);
278 }
279 }
280
281 // Either a connection attempt failed, or an existing connection
282 // was unexpectedly terminated.
283 void on_context_failed()
284 {
285 pa::connect_async(context);
286 }
287
288 // Something changed on the sink with index idx.
289 void on_sink_event_with_index(std::int32_t index)
290 {
291 config.reporter->sink_event_with_index(index);
292
293 // Update server info (active sink)
294 pa::get_server_info_async(context, main_loop, Private::query_for_server_info_finished, this);
295
296 }
297
298 void on_query_for_active_sink_finished(const pa_sink_info* info)
299 {
300 // Update active sink if a change is registered.
301 if (std::get<0>(active_sink) != info->index)
302 {
303 std::get<0>(active_sink) = info->index;
304 std::get<1>(active_sink) = info->name;
305 if (info->index != static_cast<std::uint32_t>(primary_sink_index))
306 for (auto& element : outputs)
307 std::get<1>(element) = audio::OutputState::External;
308 }
309 }
310
311 // Query for primary sink finished.
312 void on_query_for_primary_sink_finished(const pa_sink_info* info)
313 {
314 for (auto& element : outputs)
315 {
316 // Only issue state change if the change happened on the active index.
317 if (std::get<0>(active_sink) != info->index)
318 continue;
319
320 std::cout << "Checking if port is available " << " -> " << std::boolalpha << pa::is_port_available_on_sink(info, std::get<0>(element)) << std::endl;
321 bool available = pa::is_port_available_on_sink(info, std::get<0>(element));
322
323 if (available)
324 {
325 std::get<1>(element) = audio::OutputState::Earpiece;
326 continue;
327 }
328
329 audio::OutputState state;
330 if (info->index == primary_sink_index)
331 state = audio::OutputState::Speaker;
332 else
333 state = audio::OutputState::External;
334
335 std::get<1>(element) = state;
336 }
337
338 std::set<Reporter::Port> known_ports;
339 for (std::uint32_t i = 0; i < info->n_ports; i++)
340 {
341 bool is_monitored = false;
342
343 for (auto& element : outputs)
344 is_monitored |= std::regex_match(info->ports[i]->name, std::get<0>(element));
345
346 known_ports.insert(Reporter::Port
347 {
348 info->ports[i]->name,
349 info->ports[i]->description,
350 info->ports[i]->available == PA_PORT_AVAILABLE_YES,
351 is_monitored
352 });
353 }
354
355 properties.known_ports = known_ports;
356
357 // Initialize sink of primary index (onboard)
358 if (primary_sink_index == -1)
359 primary_sink_index = info->index;
360
361 config.reporter->query_for_sink_info_finished(info->name, info->index, known_ports);
362 }
363
364 void on_query_for_server_info_finished(const pa_server_info* info)
365 {
366 // We bail out if we could not determine the default sink name.
367 // In this case, we are not able to carry out audio output observation.
368 if (not info->default_sink_name)
369 {
370 config.reporter->query_for_default_sink_failed();
371 return;
372 }
373
374 // Update active sink
375 if (info->default_sink_name != std::get<1>(active_sink))
376 pa::get_index_of_sink_by_name_async(context, main_loop, info->default_sink_name, Private::query_for_active_sink_finished, this);
377
378 // Update wired output for primary sink (onboard)
379 pa::get_sink_info_by_index_async(context, main_loop, primary_sink_index, Private::query_for_primary_sink_finished, this);
380
381 if (properties.sink.get() != config.sink)
382 {
383 config.reporter->query_for_default_sink_finished(info->default_sink_name);
384 properties.sink = config.sink = info->default_sink_name;
385 pa::get_index_of_sink_by_name_async(context, main_loop, config.sink, Private::query_for_primary_sink_finished, this);
386 }
387 }
388
389 PulseAudioOutputObserver::Configuration config;
390 pa::ThreadedMainLoopPtr main_loop;
391 pa::ContextPtr context;
392 std::int32_t primary_sink_index;
393 std::tuple<uint32_t, std::string> active_sink;
394 std::vector<std::tuple<std::regex, core::Property<media::audio::OutputState>>> outputs;
395
396 struct
397 {
398 core::Property<std::string> sink;
399 core::Property<std::set<audio::PulseAudioOutputObserver::Reporter::Port>> known_ports;
400 core::Property<audio::OutputState> external_output_state{audio::OutputState::Speaker};
401 } properties;
402};
403
404bool audio::PulseAudioOutputObserver::Reporter::Port::operator==(const audio::PulseAudioOutputObserver::Reporter::Port& rhs) const
405{
406 return name == rhs.name;
407}
408
409bool audio::PulseAudioOutputObserver::Reporter::Port::operator<(const audio::PulseAudioOutputObserver::Reporter::Port& rhs) const
410{
411 return name < rhs.name;
412}
413
414audio::PulseAudioOutputObserver::Reporter::~Reporter()
415{
416}
417
418void audio::PulseAudioOutputObserver::Reporter::connected_to_pulse_audio()
419{
420}
421
422void audio::PulseAudioOutputObserver::Reporter::query_for_default_sink_failed()
423{
424}
425
426void audio::PulseAudioOutputObserver::Reporter::query_for_default_sink_finished(const std::string&)
427{
428}
429
430void audio::PulseAudioOutputObserver::Reporter::query_for_sink_info_finished(const std::string&, std::uint32_t, const std::set<Port>&)
431{
432}
433
434void audio::PulseAudioOutputObserver::Reporter::sink_event_with_index(std::uint32_t)
435{
436}
437
438// Constructs a new instance, or throws std::runtime_error
439// if connection to pulseaudio fails.
440audio::PulseAudioOutputObserver::PulseAudioOutputObserver(const Configuration& config) : d{new Private{config}}
441{
442 if (not d->config.reporter) throw std::runtime_error
443 {
444 "PulseAudioOutputObserver: Cannot construct for invalid reporter instance."
445 };
446}
447
448// We provide the name of the sink we are connecting to as a
449// getable/observable property. This is specifically meant for
450// consumption by test code.
451const core::Property<std::string>& audio::PulseAudioOutputObserver::sink() const
452{
453 return d->properties.sink;
454}
455
456// The set of ports that have been identified on the configured sink.
457// Specifically meant for consumption by test code.
458const core::Property<std::set<audio::PulseAudioOutputObserver::Reporter::Port>>& audio::PulseAudioOutputObserver::known_ports() const
459{
460 return d->properties.known_ports;
461}
462
463// Getable/observable property holding the state of external outputs.
464const core::Property<audio::OutputState>& audio::PulseAudioOutputObserver::external_output_state() const
465{
466 return d->properties.external_output_state;
467}
0468
=== added file 'src/core/media/audio/pulse_audio_output_observer.h'
--- src/core/media/audio/pulse_audio_output_observer.h 1970-01-01 00:00:00 +0000
+++ src/core/media/audio/pulse_audio_output_observer.h 2015-03-19 00:04:25 +0000
@@ -0,0 +1,128 @@
1/*
2 * Copyright © 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 * Ricardo Mendoza <ricardo.mendoza@canonical.com>
18 */
19#ifndef CORE_UBUNTU_MEDIA_AUDIO_PULSE_AUDIO_OUTPUT_OBSERVER_H_
20#define CORE_UBUNTU_MEDIA_AUDIO_PULSE_AUDIO_OUTPUT_OBSERVER_H_
21
22#include <core/media/audio/output_observer.h>
23
24#include <iosfwd>
25#include <memory>
26#include <regex>
27
28namespace core
29{
30namespace ubuntu
31{
32namespace media
33{
34namespace audio
35{
36// Implements the audio::OutputObserver interface
37// relying on pulse to query the connected ports
38// of the primary card of the system.
39class PulseAudioOutputObserver : public OutputObserver
40{
41public:
42 // Save us some typing.
43 typedef std::shared_ptr<PulseAudioOutputObserver> Ptr;
44
45 // Reporter is responsible for surfacing events from the implementation
46 // that help in resolving/tracking down issues. Default implementation is empty.
47 struct Reporter
48 {
49 // To save us some typing.
50 typedef std::shared_ptr<Reporter> Ptr;
51
52 // Simple type to help in reporting.
53 struct Port
54 {
55 // Returns true iff the name of both ports are equal.
56 bool operator==(const Port& rhs) const;
57 // Returns true iff the name of the ports differ.
58 bool operator<(const Port& rhs) const;
59
60 std::string name; // The name of the port.
61 std::string description; // Human-readable description of the port.
62 bool is_available; // True if the port is available.
63 bool is_monitored; // True if the port is monitored by the observer.
64 };
65
66 virtual ~Reporter();
67 // connected_to_pulse_audio is called when a connection with pulse has been established.
68 virtual void connected_to_pulse_audio();
69 // query_for_default_sink_failed is called when no default sink was returned.
70 virtual void query_for_default_sink_failed();
71 // query_for_default_sink_finished is called when the default sink query against pulse
72 // has finished, reporting the name of the sink to observers.
73 virtual void query_for_default_sink_finished(const std::string& sink_name);
74 // query_for_sink_info_finished is called when a query for information about a specific sink
75 // has finished, reporting the name, index of the sink as well as the set of ports known to the sink.
76 virtual void query_for_sink_info_finished(const std::string& name, std::uint32_t index, const std::set<Port>& known_ports);
77 // sink_event_with_index is called when something happened on a sink, reporing the index of the
78 // sink.
79 virtual void sink_event_with_index(std::uint32_t index);
80 };
81
82 // Construction time arguments go here
83 struct Configuration
84 {
85 // Name of the sink that we should consider.
86 std::string sink
87 {
88 // A special value that requests the implementation to
89 // query pulseaudio for the default configured sink.
90 "query.from.server"
91 };
92 // Output port name patterns that should be observed on the configured sink.
93 // All patterns have to be valid regular expressions.
94 std::vector<std::regex> output_port_patterns
95 {
96 // Any port is considered with this special value.
97 std::regex{".+"}
98 };
99 // The Reporter instance that the implementation reports
100 // events to. Must not be null.
101 Reporter::Ptr reporter{std::make_shared<Reporter>()};
102 };
103
104 // Constructs a new instance, throws:
105 // * std::runtime_error if connection to pulseaudio fails.
106 // * std::runtime_error if reporter instance is null.
107 PulseAudioOutputObserver(const Configuration&);
108
109 // We provide the name of the sink we are connecting to as a
110 // getable/observable property. This is specifically meant for
111 // consumption by test code.
112 const core::Property<std::string>& sink() const;
113 // The set of ports that have been identified on the configured sink.
114 // Specifically meant for consumption by test code.
115 const core::Property<std::set<Reporter::Port>>& known_ports() const;
116 // Getable/observable property holding the state of external outputs.
117 const core::Property<OutputState>& external_output_state() const override;
118
119private:
120 struct Private;
121 std::shared_ptr<Private> d;
122};
123}
124}
125}
126}
127
128#endif // CORE_UBUNTU_MEDIA_AUDIO_PULSE_AUDIO_OUTPUT_OBSERVER_H_
0129
=== modified file 'src/core/media/gstreamer/playbin.cpp'
--- src/core/media/gstreamer/playbin.cpp 2015-03-19 00:04:24 +0000
+++ src/core/media/gstreamer/playbin.cpp 2015-03-19 00:04:25 +0000
@@ -406,6 +406,9 @@
406 };406 };
407407
408 auto ret = gst_element_set_state(pipeline, new_state);408 auto ret = gst_element_set_state(pipeline, new_state);
409
410 std::cout << __PRETTY_FUNCTION__ << ": requested state change." << std::endl;
411
409 bool result = false; GstState current, pending;412 bool result = false; GstState current, pending;
410 switch(ret)413 switch(ret)
411 {414 {
@@ -419,7 +422,7 @@
419 pipeline,422 pipeline,
420 &current,423 &current,
421 &pending,424 &pending,
422 state_change_timeout.count()); 425 state_change_timeout.count());
423 break;426 break;
424 }427 }
425428
426429
=== modified file 'src/core/media/service_implementation.cpp'
--- src/core/media/service_implementation.cpp 2015-03-19 00:04:24 +0000
+++ src/core/media/service_implementation.cpp 2015-03-19 00:04:25 +0000
@@ -22,6 +22,7 @@
2222
23#include "service_implementation.h"23#include "service_implementation.h"
2424
25#include "audio/output_observer.h"
25#include "client_death_observer.h"26#include "client_death_observer.h"
26#include "call-monitor/call_monitor.h"27#include "call-monitor/call_monitor.h"
27#include "player_configuration.h"28#include "player_configuration.h"
@@ -57,349 +58,10 @@
57 display_state_lock(power_state_controller->display_state_lock()),58 display_state_lock(power_state_controller->display_state_lock()),
58 client_death_observer(media::platform_default_client_death_observer()),59 client_death_observer(media::platform_default_client_death_observer()),
59 recorder_observer(media::make_platform_default_recorder_observer()),60 recorder_observer(media::make_platform_default_recorder_observer()),
60 pulse_mainloop_api(nullptr),61 audio_output_observer(media::audio::make_platform_default_output_observer()),
61 pulse_context(nullptr),62 audio_output_state(media::audio::OutputState::Speaker),
62 headphones_connected(false),
63 a2dp_connected(false),
64 primary_idx(-1),
65 call_monitor(new CallMonitor)63 call_monitor(new CallMonitor)
66 {64 {
67 // Spawn pulse watchdog
68 pulse_mainloop = nullptr;
69 pulse_worker = std::move(std::thread([this]()
70 {
71 std::unique_lock<std::mutex> lk(pulse_mutex);
72 pcv.wait(lk,
73 [this]{
74 if (pulse_mainloop != nullptr || pulse_context != nullptr)
75 {
76 // We come from instance death, destroy and create.
77 if (pulse_context != nullptr)
78 {
79 pa_threaded_mainloop_lock(pulse_mainloop);
80 pa_operation *o;
81 o = pa_context_drain(pulse_context,
82 [](pa_context *context, void *userdata)
83 {
84 (void) context;
85
86 Private *p = reinterpret_cast<Private*>(userdata);
87 pa_threaded_mainloop_signal(p->mainloop(), 0);
88 }, this);
89
90 if (o)
91 {
92 while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
93 pa_threaded_mainloop_wait(pulse_mainloop);
94
95 pa_operation_unref(o);
96 }
97
98 pa_context_set_state_callback(pulse_context, NULL, NULL);
99 pa_context_set_subscribe_callback(pulse_context, NULL, NULL);
100 pa_context_disconnect(pulse_context);
101 pa_context_unref(pulse_context);
102 pulse_context = nullptr;
103 pa_threaded_mainloop_unlock(pulse_mainloop);
104 }
105 }
106
107 if (pulse_mainloop == nullptr)
108 {
109 pulse_mainloop = pa_threaded_mainloop_new();
110
111 if (pa_threaded_mainloop_start(pulse_mainloop) != 0)
112 {
113 std::cerr << "Unable to start pulseaudio mainloop, audio output detection will not function" << std::endl;
114 pa_threaded_mainloop_free(pulse_mainloop);
115 pulse_mainloop = nullptr;
116 }
117 }
118
119 do {
120 create_pulse_context();
121 } while (pulse_context == nullptr);
122
123 // Wait for next instance death.
124 return false;
125 });
126 }));
127
128 recorder_observer->recording_state().changed().connect([this](media::RecordingState state)
129 {
130 media_recording_state_changed(state);
131 });
132 }
133
134 ~Private()
135 {
136 release_pulse_context();
137
138 if (pulse_mainloop != nullptr)
139 {
140 pa_threaded_mainloop_stop(pulse_mainloop);
141 pa_threaded_mainloop_free(pulse_mainloop);
142 pulse_mainloop = nullptr;
143 }
144
145 if (pulse_worker.joinable())
146 pulse_worker.join();
147 }
148
149 void media_recording_state_changed(media::RecordingState state)
150 {
151 if (state == media::RecordingState::started)
152 {
153 display_state_lock->request_acquire(media::power::DisplayState::on);
154 pause_playback();
155 }
156 else if (state == media::RecordingState::stopped)
157 {
158 display_state_lock->request_release(media::power::DisplayState::on);
159 }
160 }
161
162 pa_threaded_mainloop *mainloop()
163 {
164 return pulse_mainloop;
165 }
166
167 bool is_port_available(pa_card_port_info **ports, uint32_t n_ports, const char *name)
168 {
169 bool ret = false;
170
171 if (ports != nullptr && n_ports > 0 && name != nullptr)
172 {
173 for (uint32_t i=0; i<n_ports; i++)
174 {
175 if (strstr(ports[i]->name, name) != nullptr && ports[i]->available != PA_PORT_AVAILABLE_NO)
176 {
177 ret = true;
178 break;
179 }
180 }
181 }
182
183 return ret;
184 }
185
186 void update_wired_output()
187 {
188 const pa_operation *o = pa_context_get_card_info_by_index(pulse_context, primary_idx,
189 [](pa_context *context, const pa_card_info *info, int eol, void *userdata)
190 {
191 (void) context;
192 (void) eol;
193
194 if (info == nullptr || userdata == nullptr)
195 return;
196
197 Private *p = reinterpret_cast<Private*>(userdata);
198 if (p->is_port_available(info->ports, info->n_ports, "output-wired"))
199 {
200 if (!p->headphones_connected)
201 std::cout << "Wired headphones connected" << std::endl;
202 p->headphones_connected = true;
203 }
204 else if (p->headphones_connected == true)
205 {
206 std::cout << "Wired headphones disconnected" << std::endl;
207 p->headphones_connected = false;
208 p->pause_playback_if_necessary(std::get<0>(p->active_sink));
209 }
210 }, this);
211 (void) o;
212 }
213
214 void pause_playback_if_necessary(int index)
215 {
216 // Catch uninitialized case (active index == -1)
217 if (std::get<0>(active_sink) == -1)
218 return;
219
220 if (headphones_connected)
221 return;
222
223 // No headphones/fallback on primary sink? Pause.
224 if (index == primary_idx)
225 pause_playback();
226 }
227
228 void set_active_sink(const char *name)
229 {
230 const pa_operation *o = pa_context_get_sink_info_by_name(pulse_context, name,
231 [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
232 {
233 (void) context;
234
235 if (eol)
236 return;
237
238 Private *p = reinterpret_cast<Private*>(userdata);
239 std::tuple<uint32_t, uint32_t, std::string> new_sink(std::make_tuple(i->index, i->card, i->name));
240
241 printf("pulsesink: active_sink=('%s',%d,%d) -> ('%s',%d,%d)\n",
242 std::get<2>(p->active_sink).c_str(), std::get<0>(p->active_sink),
243 std::get<1>(p->active_sink), i->name, i->index, i->card);
244
245 p->pause_playback_if_necessary(i->index);
246 p->active_sink = new_sink;
247 }, this);
248
249 (void) o;
250 }
251
252 void update_active_sink()
253 {
254 const pa_operation *o = pa_context_get_server_info(pulse_context,
255 [](pa_context *context, const pa_server_info *i, void *userdata)
256 {
257 (void) context;
258
259 Private *p = reinterpret_cast<Private*>(userdata);
260 if (i->default_sink_name != std::get<2>(p->active_sink))
261 p->set_active_sink(i->default_sink_name);
262 p->update_wired_output();
263 }, this);
264
265 (void) o;
266 }
267
268 void create_pulse_context()
269 {
270 if (pulse_context != nullptr)
271 return;
272
273 active_sink = std::make_tuple(-1, -1, "");
274
275 bool keep_going = true, ok = true;
276
277 pulse_mainloop_api = pa_threaded_mainloop_get_api(pulse_mainloop);
278 pa_threaded_mainloop_lock(pulse_mainloop);
279
280 pulse_context = pa_context_new(pulse_mainloop_api, "MediaHubPulseContext");
281 pa_context_set_state_callback(pulse_context,
282 [](pa_context *context, void *userdata)
283 {
284 (void) context;
285 Private *p = reinterpret_cast<Private*>(userdata);
286 // Signals the pa_threaded_mainloop_wait below to proceed
287 pa_threaded_mainloop_signal(p->mainloop(), 0);
288 }, this);
289
290 if (pulse_context == nullptr)
291 {
292 std::cerr << "Unable to create new pulseaudio context" << std::endl;
293 pa_threaded_mainloop_unlock(pulse_mainloop);
294 return;
295 }
296
297 pa_context_connect(pulse_context, nullptr, pa_context_flags_t((int) PA_CONTEXT_NOAUTOSPAWN | (int) PA_CONTEXT_NOFAIL), nullptr);
298 pa_threaded_mainloop_wait(pulse_mainloop);
299
300 while (keep_going)
301 {
302 switch (pa_context_get_state(pulse_context))
303 {
304 case PA_CONTEXT_CONNECTING: // Wait for service to be available
305 case PA_CONTEXT_AUTHORIZING:
306 case PA_CONTEXT_SETTING_NAME:
307 break;
308
309 case PA_CONTEXT_READY:
310 std::cout << "Pulseaudio connection established." << std::endl;
311 keep_going = false;
312 break;
313
314 case PA_CONTEXT_FAILED:
315 case PA_CONTEXT_TERMINATED:
316 keep_going = false;
317 ok = false;
318 break;
319
320 default:
321 std::cerr << "Pulseaudio connection failure: " << pa_strerror(pa_context_errno(pulse_context));
322 keep_going = false;
323 ok = false;
324 }
325
326 if (keep_going)
327 pa_threaded_mainloop_wait(pulse_mainloop);
328 }
329
330 if (ok)
331 {
332 pa_context_set_state_callback(pulse_context,
333 [](pa_context *context, void *userdata)
334 {
335 (void) context;
336 (void) userdata;
337 Private *p = reinterpret_cast<Private*>(userdata);
338 std::unique_lock<std::mutex> lk(p->pulse_mutex);
339 switch (pa_context_get_state(context))
340 {
341 case PA_CONTEXT_FAILED:
342 case PA_CONTEXT_TERMINATED:
343 p->pcv.notify_all();
344 break;
345 default:
346 break;
347 }
348 }, this);
349
350 //FIXME: Get index for "sink.primary", the default onboard card on Touch.
351 pa_context_get_sink_info_by_name(pulse_context, "sink.primary",
352 [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
353 {
354 (void) context;
355
356 if (eol)
357 return;
358
359 Private *p = reinterpret_cast<Private*>(userdata);
360 p->primary_idx = i->index;
361 p->update_wired_output();
362 }, this);
363
364 update_active_sink();
365
366 pa_context_set_subscribe_callback(pulse_context,
367 [](pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
368 {
369 (void) context;
370 (void) idx;
371
372 if (userdata == nullptr)
373 return;
374
375 Private *p = reinterpret_cast<Private*>(userdata);
376 if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
377 {
378 p->update_active_sink();
379 }
380 }, this);
381 pa_context_subscribe(pulse_context, PA_SUBSCRIPTION_MASK_SINK, nullptr, this);
382 }
383 else
384 {
385 std::cerr << "Connection to pulseaudio failed or was dropped." << std::endl;
386 pa_context_unref(pulse_context);
387 pulse_context = nullptr;
388 }
389
390 pa_threaded_mainloop_unlock(pulse_mainloop);
391 }
392
393 void release_pulse_context()
394 {
395 if (pulse_context != nullptr)
396 {
397 pa_threaded_mainloop_lock(pulse_mainloop);
398 pa_context_disconnect(pulse_context);
399 pa_context_unref(pulse_context);
400 pa_threaded_mainloop_unlock(pulse_mainloop);
401 pulse_context = nullptr;
402 }
403 }65 }
40466
405 media::ServiceImplementation::Configuration configuration;67 media::ServiceImplementation::Configuration configuration;
@@ -411,22 +73,9 @@
411 media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;73 media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;
412 media::ClientDeathObserver::Ptr client_death_observer;74 media::ClientDeathObserver::Ptr client_death_observer;
413 media::RecorderObserver::Ptr recorder_observer;75 media::RecorderObserver::Ptr recorder_observer;
414 // Pulse-specific76 media::audio::OutputObserver::Ptr audio_output_observer;
415 pa_mainloop_api *pulse_mainloop_api;77 media::audio::OutputState audio_output_state;
416 pa_threaded_mainloop *pulse_mainloop;
417 pa_context *pulse_context;
418 std::thread pulse_worker;
419 std::mutex pulse_mutex;
420 std::condition_variable pcv;
421 bool headphones_connected;
422 bool a2dp_connected;
423 std::tuple<int, int, std::string> active_sink;
424 int primary_idx;
42578
426 // Gets signaled when both the headphone jack is removed or an A2DP device is
427 // disconnected and playback needs pausing. Also gets signaled when recording
428 // begins.
429 core::Signal<void> pause_playback;
430 std::unique_ptr<CallMonitor> call_monitor;79 std::unique_ptr<CallMonitor> call_monitor;
431 std::list<media::Player::PlayerKey> paused_sessions;80 std::list<media::Player::PlayerKey> paused_sessions;
432};81};
@@ -456,10 +105,22 @@
456 resume_multimedia_session();105 resume_multimedia_session();
457 });106 });
458107
459 d->pause_playback.connect([this]()108 d->audio_output_observer->external_output_state().changed().connect([this](audio::OutputState state)
460 {109 {
461 std::cout << "Got pause_playback signal, pausing all multimedia sessions" << std::endl;110 switch (state)
462 pause_all_multimedia_sessions();111 {
112 case audio::OutputState::Earpiece:
113 std::cout << "AudioOutputObserver reports that output is now Headphones/Headset." << std::endl;
114 break;
115 case audio::OutputState::Speaker:
116 std::cout << "AudioOutputObserver reports that output is now Speaker." << std::endl;
117 pause_all_multimedia_sessions();
118 break;
119 case audio::OutputState::External:
120 std::cout << "AudioOutputObserver reports that output is now External." << std::endl;
121 break;
122 }
123 d->audio_output_state = state;
463 });124 });
464125
465 d->call_monitor->on_change([this](CallMonitor::State state) {126 d->call_monitor->on_change([this](CallMonitor::State state) {
@@ -475,6 +136,19 @@
475 break;136 break;
476 }137 }
477 });138 });
139
140 d->recorder_observer->recording_state().changed().connect([this](RecordingState state)
141 {
142 if (state == media::RecordingState::started)
143 {
144 d->display_state_lock->request_acquire(media::power::DisplayState::on);
145 pause_all_multimedia_sessions();
146 }
147 else if (state == media::RecordingState::stopped)
148 {
149 d->display_state_lock->request_release(media::power::DisplayState::on);
150 }
151 });
478}152}
479153
480media::ServiceImplementation::~ServiceImplementation()154media::ServiceImplementation::~ServiceImplementation()

Subscribers

People subscribed via source and target branches