Ring Daemon 16.0.0
Loading...
Searching...
No Matches
audiostream.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 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
26using namespace std::literals;
27
28namespace jami {
29
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,
40 : onReady_(std::move(onReady))
41 , onData_(std::move(onData))
42 , audiostream_(nullptr)
43 , mainloop_(m)
44 , audioType_(type)
45{
46 pa_sample_spec sample_spec = {format,
48 infos.channel_map.channels};
49
50 JAMI_DEBUG("{}: Creating stream with device {} ({}, {}Hz, {} channels)",
51 desc,
52 infos.name,
53 pa_sample_format_to_string(sample_spec.format),
55 infos.channel_map.channels);
56
57 assert(pa_sample_spec_valid(&sample_spec));
59
60 std::unique_ptr<pa_proplist, decltype(pa_proplist_free)&> pl(pa_proplist_new(),
62 pa_proplist_sets(pl.get(), PA_PROP_FILTER_WANT, "echo-cancel");
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 audiostream_ = pa_stream_new_with_proplist(c,
74 desc,
75 &sample_spec,
76 &infos.channel_map,
77 ec ? pl.get() : nullptr);
78 if (!audiostream_) {
79 JAMI_ERR("%s: pa_stream_new() failed : %s", desc, pa_strerror(pa_context_errno(c)));
80 throw std::runtime_error("Unable to create stream\n");
81 }
82
84 attributes.maxlength = pa_usec_to_bytes(160 * PA_USEC_PER_MSEC, &sample_spec);
85 attributes.tlength = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
86 attributes.prebuf = 0;
87 attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
88 attributes.minreq = (uint32_t) -1;
89
91 audiostream_,
92 [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->stateChanged(s); },
93 this);
95 audiostream_,
96 [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->moved(s); },
97 this);
98
99 constexpr pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(
101
104 audiostream_,
105 [](pa_stream* /*s*/, size_t bytes, void* userdata) {
106 static_cast<AudioStream*>(userdata)->onData_(bytes);
107 },
108 this);
109
110 pa_stream_connect_playback(audiostream_,
111 infos.name.empty() ? nullptr : infos.name.c_str(),
112 &attributes,
113 flags,
114 nullptr,
115 nullptr);
116 } else if (type == AudioDeviceType::CAPTURE) {
118 audiostream_,
119 [](pa_stream* /*s*/, size_t bytes, void* userdata) {
120 static_cast<AudioStream*>(userdata)->onData_(bytes);
121 },
122 this);
123
124 pa_stream_connect_record(audiostream_,
125 infos.name.empty() ? nullptr : infos.name.c_str(),
126 &attributes,
127 flags);
128 }
129}
130
131void
133{
134 // make sure we don't get any further callback
135 pa_stream_set_write_callback(s, nullptr, nullptr);
136 pa_stream_set_read_callback(s, nullptr, nullptr);
137 pa_stream_set_moved_callback(s, nullptr, nullptr);
138 pa_stream_set_underflow_callback(s, nullptr, nullptr);
139 pa_stream_set_overflow_callback(s, nullptr, nullptr);
140 pa_stream_set_suspended_callback(s, nullptr, nullptr);
141 pa_stream_set_started_callback(s, nullptr, nullptr);
142}
143
144void
146{
148 pa_stream_set_state_callback(s, nullptr, nullptr);
151}
152
157
158void
160{
161 pa_stream_cork(audiostream_, 0, nullptr, nullptr);
162
163 // trigger echo cancel check
164 moved(audiostream_);
165}
166
167void
169{
170 if (not audiostream_)
171 return;
172 JAMI_DBG("Destroying stream with device %s", pa_stream_get_device_name(audiostream_));
173 if (pa_stream_get_state(audiostream_) == PA_STREAM_CREATING) {
174 disconnectStream(audiostream_);
176 audiostream_, [](pa_stream* s, void*) { destroyStream(s); }, nullptr);
177 } else {
178 destroyStream(audiostream_);
179 }
180 audiostream_ = nullptr;
181
182 std::unique_lock lock(mutex_);
183 for (auto op : ongoing_ops)
185 // wait for all operations to end
186 cond_.wait(lock, [this]{ return ongoing_ops.empty(); });
187}
188
189void
190AudioStream::moved(pa_stream* s)
191{
192 audiostream_ = s;
193 JAMI_LOG("[audiostream] Stream moved: {:d}, {:s}",
196
197 if (audioType_ == AudioDeviceType::CAPTURE) {
198 // check for echo cancel
199 const char* name = pa_stream_get_device_name(s);
200 if (!name) {
201 JAMI_ERR("[audiostream] moved() unable to get audio stream device");
202 return;
203 }
204
207 name,
208 [](pa_context* /*c*/, const pa_source_info* i, int /*eol*/, void* userdata) {
209 AudioStream* thisPtr = (AudioStream*) userdata;
210 // this closure gets called twice by pulse for some reason
211 // the 2nd time, i is invalid
212 if (!i) {
213 // JAMI_ERROR("[audiostream] source info not found");
214 return;
215 }
216 if (!thisPtr) {
217 JAMI_ERROR("[audiostream] AudioStream pointer became invalid during pa_source_info_cb_t callback!");
218 return;
219 }
220
221 // string compare
222 bool usingEchoCancel = std::string_view(i->driver) == "module-echo-cancel.c"sv;
223 JAMI_WARNING("[audiostream] capture stream using pulse echo cancel module? {} ({})",
224 usingEchoCancel ? "yes" : "no",
225 i->name);
226 thisPtr->echoCancelCb(usingEchoCancel);
227 },
228 this);
229
230 std::lock_guard lock(mutex_);
232 static_cast<AudioStream*>(userdata)->opEnded(op);
233 }, this);
234 ongoing_ops.emplace(op);
235 }
236}
237
238void
239AudioStream::opEnded(pa_operation* op)
240{
241 std::lock_guard lock(mutex_);
242 ongoing_ops.erase(op);
244 cond_.notify_all();
245}
246
247void
248AudioStream::stateChanged(pa_stream* s)
249{
250 // UNUSED char str[PA_SAMPLE_SPEC_SNPRINT_MAX];
251
252 switch (pa_stream_get_state(s)) {
254 JAMI_DBG("Stream is creating…");
255 break;
256
258 JAMI_DBG("Stream is terminating…");
259 break;
260
261 case PA_STREAM_READY:
262 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 onReady_();
270 break;
271
273 JAMI_DBG("Stream unconnected");
274 break;
275
276 case PA_STREAM_FAILED:
277 default:
279 break;
280 }
281}
282
283bool
285{
286 if (!audiostream_)
287 return false;
288
289 return pa_stream_get_state(audiostream_) == PA_STREAM_READY;
290}
291
292} // namespace jami
std::function< void()> OnReady
Definition audiostream.h:63
std::function< void(size_t)> OnData
Definition audiostream.h:64
AudioFormat format() const
AudioStream(pa_context *, pa_threaded_mainloop *, const char *, AudioDeviceType, unsigned, pa_sample_format_t, const PaDeviceInfos &, bool, OnReady onReady, OnData onData)
Constructor.
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_ERROR(formatstr,...)
Definition logger.h:228
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:226
#define JAMI_WARNING(formatstr,...)
Definition logger.h:227
#define JAMI_LOG(formatstr,...)
Definition logger.h:225
void emitSignal(Args... args)
Definition ring_signal.h:64
void destroyStream(pa_stream *s)
void disconnectStream(pa_stream *s)
AudioDeviceType
Definition audiolayer.h:58
Convenience structure to hold PulseAudio device propreties such as supported channel number etc.
Definition pulselayer.h:41
std::string name
Definition pulselayer.h:43
pa_channel_map channel_map
Definition pulselayer.h:46