Ring Daemon
Loading...
Searching...
No Matches
audiostream.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 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, samplrate, infos.channel_map.channels};
47
48 JAMI_DEBUG("{}: Creating stream with device {} ({}, {}Hz, {} channels)",
49 desc,
50 infos.name,
51 pa_sample_format_to_string(sample_spec.format),
53 infos.channel_map.channels);
54
55 assert(pa_sample_spec_valid(&sample_spec));
57
58 std::unique_ptr<pa_proplist, decltype(pa_proplist_free)&> pl(pa_proplist_new(), pa_proplist_free);
59 pa_proplist_sets(pl.get(), PA_PROP_FILTER_WANT, "echo-cancel");
60 pa_proplist_sets(pl.get(),
61 "filter.apply.echo-cancel.parameters", // needs pulseaudio >= 11.0
62 "use_volume_sharing=0" // share volume with master sink/source
63 " use_master_format=1" // use format/rate/channels from master sink/source
64 " aec_args=\""
65 "digital_gain_control=1"
66 " analog_gain_control=0"
67 " experimental_agc=1"
68 "\"");
69
70 audiostream_ = pa_stream_new_with_proplist(c, desc, &sample_spec, &infos.channel_map, ec ? pl.get() : nullptr);
71 if (!audiostream_) {
72 JAMI_ERR("%s: pa_stream_new() failed : %s", desc, pa_strerror(pa_context_errno(c)));
73 throw std::runtime_error("Unable to create stream\n");
74 }
75
77 attributes.maxlength = pa_usec_to_bytes(160 * PA_USEC_PER_MSEC, &sample_spec);
78 attributes.tlength = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
79 attributes.prebuf = 0;
80 attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
81 attributes.minreq = (uint32_t) -1;
82
84 audiostream_,
85 [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->stateChanged(s); },
86 this);
88 audiostream_, [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->moved(s); }, this);
89
90 constexpr pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(
92
95 audiostream_,
96 [](pa_stream* /*s*/, size_t bytes, void* userdata) { static_cast<AudioStream*>(userdata)->onData_(bytes); },
97 this);
98
99 pa_stream_connect_playback(audiostream_,
100 infos.name.empty() ? nullptr : infos.name.c_str(),
101 &attributes,
102 flags,
103 nullptr,
104 nullptr);
105 } else if (type == AudioDeviceType::CAPTURE) {
107 audiostream_,
108 [](pa_stream* /*s*/, size_t bytes, void* userdata) { static_cast<AudioStream*>(userdata)->onData_(bytes); },
109 this);
110
111 pa_stream_connect_record(audiostream_, infos.name.empty() ? nullptr : infos.name.c_str(), &attributes, flags);
112 }
113}
114
115void
117{
118 // make sure we don't get any further callback
119 pa_stream_set_write_callback(s, nullptr, nullptr);
120 pa_stream_set_read_callback(s, nullptr, nullptr);
121 pa_stream_set_moved_callback(s, nullptr, nullptr);
122 pa_stream_set_underflow_callback(s, nullptr, nullptr);
123 pa_stream_set_overflow_callback(s, nullptr, nullptr);
124 pa_stream_set_suspended_callback(s, nullptr, nullptr);
125 pa_stream_set_started_callback(s, nullptr, nullptr);
126}
127
128void
130{
132 pa_stream_set_state_callback(s, nullptr, nullptr);
135}
136
141
142void
144{
145 pa_stream_cork(audiostream_, 0, nullptr, nullptr);
146
147 // trigger echo cancel check
148 moved(audiostream_);
149}
150
151void
153{
154 if (not audiostream_)
155 return;
156 JAMI_DBG("Destroying stream with device %s", pa_stream_get_device_name(audiostream_));
157 if (pa_stream_get_state(audiostream_) == PA_STREAM_CREATING) {
158 disconnectStream(audiostream_);
159 pa_stream_set_state_callback(audiostream_, [](pa_stream* s, void*) { destroyStream(s); }, nullptr);
160 } else {
161 destroyStream(audiostream_);
162 }
163 audiostream_ = nullptr;
164
165 // pa_operation_cancel calls the operation's state callback synchronously, so
166 // copy ongoing_ops to avoid erasing its elements while iterating over it.
167 auto ops = ongoing_ops;
168 for (auto op : ops)
170}
171
172void
173AudioStream::moved(pa_stream* s)
174{
175 audiostream_ = s;
176 JAMI_LOG("[audiostream] Stream moved: {:d}, {:s}", pa_stream_get_index(s), pa_stream_get_device_name(s));
177
178 if (audioType_ == AudioDeviceType::CAPTURE) {
179 // check for echo cancel
180 const char* name = pa_stream_get_device_name(s);
181 if (!name) {
182 JAMI_ERR("[audiostream] moved() unable to get audio stream device");
183 return;
184 }
185
188 name,
189 [](pa_context* /*c*/, const pa_source_info* i, int /*eol*/, void* userdata) {
190 AudioStream* thisPtr = (AudioStream*) userdata;
191 // this closure gets called twice by pulse for some reason
192 // the 2nd time, i is invalid
193 if (!i) {
194 // JAMI_ERROR("[audiostream] source info not found");
195 return;
196 }
197 if (!thisPtr) {
198 JAMI_ERROR("[audiostream] AudioStream pointer became invalid during pa_source_info_cb_t callback!");
199 return;
200 }
201
202 // string compare
203 bool usingEchoCancel = std::string_view(i->driver) == "module-echo-cancel.c"sv;
204 JAMI_WARNING("[audiostream] capture stream using pulse echo cancel module? {} ({})",
205 usingEchoCancel ? "yes" : "no",
206 i->name);
207 thisPtr->echoCancelCb(usingEchoCancel);
208 },
209 this);
210
212 op, [](pa_operation* op, void* userdata) { static_cast<AudioStream*>(userdata)->opEnded(op); }, this);
213 ongoing_ops.emplace(op);
214 }
215}
216
217void
218AudioStream::opEnded(pa_operation* op)
219{
220 ongoing_ops.erase(op);
222}
223
224void
225AudioStream::stateChanged(pa_stream* s)
226{
227 // UNUSED char str[PA_SAMPLE_SPEC_SNPRINT_MAX];
228
229 switch (pa_stream_get_state(s)) {
231 JAMI_DBG("Stream is creating…");
232 break;
233
235 JAMI_DBG("Stream is terminating…");
236 break;
237
238 case PA_STREAM_READY:
239 JAMI_DBG("Stream successfully created, connected to %s", pa_stream_get_device_name(s));
240 // JAMI_DBG("maxlength %u", pa_stream_get_buffer_attr(s)->maxlength);
241 // JAMI_DBG("tlength %u", pa_stream_get_buffer_attr(s)->tlength);
242 // JAMI_DBG("prebuf %u", pa_stream_get_buffer_attr(s)->prebuf);
243 // JAMI_DBG("minreq %u", pa_stream_get_buffer_attr(s)->minreq);
244 // JAMI_DBG("fragsize %u", pa_stream_get_buffer_attr(s)->fragsize);
245 // JAMI_DBG("samplespec %s", pa_sample_spec_snprint(str, sizeof(str), pa_stream_get_sample_spec(s)));
246 onReady_();
247 break;
248
250 JAMI_DBG("Stream unconnected");
251 break;
252
253 case PA_STREAM_FAILED:
254 default:
256 break;
257 }
258}
259
260bool
262{
263 if (!audiostream_)
264 return false;
265
266 return pa_stream_get_state(audiostream_) == PA_STREAM_READY;
267}
268
269} // 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:230
#define JAMI_ERROR(formatstr,...)
Definition logger.h:243
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:238
#define JAMI_WARNING(formatstr,...)
Definition logger.h:242
#define JAMI_LOG(formatstr,...)
Definition logger.h:237
void emitSignal(Args... args)
Definition jami_signal.h:64
void destroyStream(pa_stream *s)
AudioDeviceType
Definition audiolayer.h:57
void disconnectStream(pa_stream *s)
Convenience structure to hold PulseAudio device propreties such as supported channel number etc.
Definition pulselayer.h:43
std::string name
Definition pulselayer.h:45
pa_channel_map channel_map
Definition pulselayer.h:48