Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "audiostream.h"
19 : #include "pulselayer.h"
20 : #include "logger.h"
21 : #include "compiler_intrinsics.h"
22 :
23 : #include <string_view>
24 : #include <stdexcept>
25 :
26 : using namespace std::literals;
27 :
28 : namespace jami {
29 :
30 0 : AudioStream::AudioStream(pa_context* c,
31 : pa_threaded_mainloop* m,
32 : const char* desc,
33 : AudioDeviceType type,
34 : unsigned samplrate,
35 : pa_sample_format_t format,
36 : const PaDeviceInfos& infos,
37 : bool ec,
38 : OnReady onReady,
39 0 : OnData onData)
40 0 : : onReady_(std::move(onReady))
41 0 : , onData_(std::move(onData))
42 0 : , audiostream_(nullptr)
43 0 : , mainloop_(m)
44 0 : , audioType_(type)
45 : {
46 : pa_sample_spec sample_spec = {format,
47 : samplrate,
48 0 : infos.channel_map.channels};
49 :
50 0 : JAMI_DEBUG("{}: Creating stream with device {} ({}, {}Hz, {} channels)",
51 : desc,
52 : infos.name,
53 : pa_sample_format_to_string(sample_spec.format),
54 : samplrate,
55 : infos.channel_map.channels);
56 :
57 0 : assert(pa_sample_spec_valid(&sample_spec));
58 0 : assert(pa_channel_map_valid(&infos.channel_map));
59 :
60 : std::unique_ptr<pa_proplist, decltype(pa_proplist_free)&> pl(pa_proplist_new(),
61 0 : pa_proplist_free);
62 0 : pa_proplist_sets(pl.get(), PA_PROP_FILTER_WANT, "echo-cancel");
63 0 : pa_proplist_sets(
64 : pl.get(), "filter.apply.echo-cancel.parameters", // needs pulseaudio >= 11.0
65 : "use_volume_sharing=0" // share volume with master sink/source
66 : " use_master_format=1" // use format/rate/channels from master sink/source
67 : " aec_args=\""
68 : "digital_gain_control=1"
69 : " analog_gain_control=0"
70 : " experimental_agc=1"
71 : "\"");
72 :
73 0 : audiostream_ = pa_stream_new_with_proplist(c,
74 : desc,
75 : &sample_spec,
76 : &infos.channel_map,
77 0 : ec ? pl.get() : nullptr);
78 0 : if (!audiostream_) {
79 0 : JAMI_ERR("%s: pa_stream_new() failed : %s", desc, pa_strerror(pa_context_errno(c)));
80 0 : throw std::runtime_error("Unable to create stream\n");
81 : }
82 :
83 : pa_buffer_attr attributes;
84 0 : attributes.maxlength = pa_usec_to_bytes(160 * PA_USEC_PER_MSEC, &sample_spec);
85 0 : attributes.tlength = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
86 0 : attributes.prebuf = 0;
87 0 : attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
88 0 : attributes.minreq = (uint32_t) -1;
89 :
90 0 : pa_stream_set_state_callback(
91 : audiostream_,
92 0 : [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->stateChanged(s); },
93 : this);
94 0 : pa_stream_set_moved_callback(
95 : audiostream_,
96 0 : [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->moved(s); },
97 : this);
98 :
99 0 : constexpr pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(
100 : PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_START_CORKED);
101 :
102 0 : if (type == AudioDeviceType::PLAYBACK || type == AudioDeviceType::RINGTONE) {
103 0 : pa_stream_set_write_callback(
104 : audiostream_,
105 0 : [](pa_stream* /*s*/, size_t bytes, void* userdata) {
106 0 : static_cast<AudioStream*>(userdata)->onData_(bytes);
107 0 : },
108 : this);
109 :
110 0 : pa_stream_connect_playback(audiostream_,
111 0 : infos.name.empty() ? nullptr : infos.name.c_str(),
112 : &attributes,
113 : flags,
114 : nullptr,
115 : nullptr);
116 0 : } else if (type == AudioDeviceType::CAPTURE) {
117 0 : pa_stream_set_read_callback(
118 : audiostream_,
119 0 : [](pa_stream* /*s*/, size_t bytes, void* userdata) {
120 0 : static_cast<AudioStream*>(userdata)->onData_(bytes);
121 0 : },
122 : this);
123 :
124 0 : pa_stream_connect_record(audiostream_,
125 0 : infos.name.empty() ? nullptr : infos.name.c_str(),
126 : &attributes,
127 : flags);
128 : }
129 0 : }
130 :
131 : void
132 0 : disconnectStream(pa_stream* s)
133 : {
134 : // make sure we don't get any further callback
135 0 : pa_stream_set_write_callback(s, nullptr, nullptr);
136 0 : pa_stream_set_read_callback(s, nullptr, nullptr);
137 0 : pa_stream_set_moved_callback(s, nullptr, nullptr);
138 0 : pa_stream_set_underflow_callback(s, nullptr, nullptr);
139 0 : pa_stream_set_overflow_callback(s, nullptr, nullptr);
140 0 : pa_stream_set_suspended_callback(s, nullptr, nullptr);
141 0 : pa_stream_set_started_callback(s, nullptr, nullptr);
142 0 : }
143 :
144 : void
145 0 : destroyStream(pa_stream* s)
146 : {
147 0 : pa_stream_disconnect(s);
148 0 : pa_stream_set_state_callback(s, nullptr, nullptr);
149 0 : disconnectStream(s);
150 0 : pa_stream_unref(s);
151 0 : }
152 :
153 0 : AudioStream::~AudioStream()
154 : {
155 0 : stop();
156 0 : }
157 :
158 : void
159 0 : AudioStream::start()
160 : {
161 0 : pa_stream_cork(audiostream_, 0, nullptr, nullptr);
162 :
163 : // trigger echo cancel check
164 0 : moved(audiostream_);
165 0 : }
166 :
167 : void
168 0 : AudioStream::stop()
169 : {
170 0 : if (not audiostream_)
171 0 : return;
172 0 : JAMI_DBG("Destroying stream with device %s", pa_stream_get_device_name(audiostream_));
173 0 : if (pa_stream_get_state(audiostream_) == PA_STREAM_CREATING) {
174 0 : disconnectStream(audiostream_);
175 0 : pa_stream_set_state_callback(
176 0 : audiostream_, [](pa_stream* s, void*) { destroyStream(s); }, nullptr);
177 : } else {
178 0 : destroyStream(audiostream_);
179 : }
180 0 : audiostream_ = nullptr;
181 :
182 0 : std::unique_lock lock(mutex_);
183 0 : for (auto op : ongoing_ops)
184 0 : pa_operation_cancel(op);
185 : // wait for all operations to end
186 0 : cond_.wait(lock, [this]{ return ongoing_ops.empty(); });
187 0 : }
188 :
189 : void
190 0 : AudioStream::moved(pa_stream* s)
191 : {
192 0 : audiostream_ = s;
193 0 : JAMI_LOG("[audiostream] Stream moved: {:d}, {:s}",
194 : pa_stream_get_index(s),
195 : pa_stream_get_device_name(s));
196 :
197 0 : if (audioType_ == AudioDeviceType::CAPTURE) {
198 : // check for echo cancel
199 0 : const char* name = pa_stream_get_device_name(s);
200 0 : if (!name) {
201 0 : JAMI_ERR("[audiostream] moved() unable to get audio stream device");
202 0 : return;
203 : }
204 :
205 0 : auto* op = pa_context_get_source_info_by_name(
206 : pa_stream_get_context(s),
207 : name,
208 0 : [](pa_context* /*c*/, const pa_source_info* i, int /*eol*/, void* userdata) {
209 0 : AudioStream* thisPtr = (AudioStream*) userdata;
210 : // this closure gets called twice by pulse for some reason
211 : // the 2nd time, i is invalid
212 0 : if (!i) {
213 : // JAMI_ERROR("[audiostream] source info not found");
214 0 : return;
215 : }
216 0 : if (!thisPtr) {
217 0 : JAMI_ERROR("[audiostream] AudioStream pointer became invalid during pa_source_info_cb_t callback!");
218 0 : return;
219 : }
220 :
221 : // string compare
222 0 : bool usingEchoCancel = std::string_view(i->driver) == "module-echo-cancel.c"sv;
223 0 : JAMI_WARNING("[audiostream] capture stream using pulse echo cancel module? {} ({})",
224 : usingEchoCancel ? "yes" : "no",
225 : i->name);
226 0 : thisPtr->echoCancelCb(usingEchoCancel);
227 : },
228 0 : this);
229 :
230 0 : std::lock_guard lock(mutex_);
231 0 : pa_operation_set_state_callback(op, [](pa_operation *op, void *userdata){
232 0 : static_cast<AudioStream*>(userdata)->opEnded(op);
233 0 : }, this);
234 0 : ongoing_ops.emplace(op);
235 0 : }
236 : }
237 :
238 : void
239 0 : AudioStream::opEnded(pa_operation* op)
240 : {
241 0 : std::lock_guard lock(mutex_);
242 0 : ongoing_ops.erase(op);
243 0 : pa_operation_unref(op);
244 0 : cond_.notify_all();
245 0 : }
246 :
247 : void
248 0 : AudioStream::stateChanged(pa_stream* s)
249 : {
250 : // UNUSED char str[PA_SAMPLE_SPEC_SNPRINT_MAX];
251 :
252 0 : switch (pa_stream_get_state(s)) {
253 0 : case PA_STREAM_CREATING:
254 0 : JAMI_DBG("Stream is creating…");
255 0 : break;
256 :
257 0 : case PA_STREAM_TERMINATED:
258 0 : JAMI_DBG("Stream is terminating…");
259 0 : break;
260 :
261 0 : case PA_STREAM_READY:
262 0 : JAMI_DBG("Stream successfully created, connected to %s", pa_stream_get_device_name(s));
263 : // JAMI_DBG("maxlength %u", pa_stream_get_buffer_attr(s)->maxlength);
264 : // JAMI_DBG("tlength %u", pa_stream_get_buffer_attr(s)->tlength);
265 : // JAMI_DBG("prebuf %u", pa_stream_get_buffer_attr(s)->prebuf);
266 : // JAMI_DBG("minreq %u", pa_stream_get_buffer_attr(s)->minreq);
267 : // JAMI_DBG("fragsize %u", pa_stream_get_buffer_attr(s)->fragsize);
268 : // JAMI_DBG("samplespec %s", pa_sample_spec_snprint(str, sizeof(str), pa_stream_get_sample_spec(s)));
269 0 : onReady_();
270 0 : break;
271 :
272 0 : case PA_STREAM_UNCONNECTED:
273 0 : JAMI_DBG("Stream unconnected");
274 0 : break;
275 :
276 0 : case PA_STREAM_FAILED:
277 : default:
278 0 : JAMI_ERR("Stream failure: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
279 0 : break;
280 : }
281 0 : }
282 :
283 : bool
284 0 : AudioStream::isReady()
285 : {
286 0 : if (!audiostream_)
287 0 : return false;
288 :
289 0 : return pa_stream_get_state(audiostream_) == PA_STREAM_READY;
290 : }
291 :
292 : } // namespace jami
|