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