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