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: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
6 : * Author: Андрей Лухнов <aol.nnov@gmail.com>
7 : * Author: Mohamed Fenjiro <mohamed.fenjiro@savoirfairelinux.com>
8 : *
9 : * This program is free software; you can redistribute it and/or modify
10 : * it under the terms of the GNU General Public License as published by
11 : * the Free Software Foundation; either version 3 of the License, or
12 : * (at your option) any later version.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program; if not, write to the Free Software
21 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 : */
23 :
24 : #include "alsalayer.h"
25 : #include "logger.h"
26 : #include "manager.h"
27 : #include "noncopyable.h"
28 : #include "client/ring_signal.h"
29 : #include "audio/ringbufferpool.h"
30 : #include "audio/ringbuffer.h"
31 : #include "audio/audioloop.h"
32 : #include "libav_utils.h"
33 :
34 : #include <fmt/core.h>
35 :
36 : #include <thread>
37 : #include <atomic>
38 : #include <chrono>
39 :
40 : namespace jami {
41 :
42 31 : AlsaLayer::AlsaLayer(const AudioPreference& pref)
43 : : AudioLayer(pref)
44 62 : , indexIn_(pref.getAlsaCardin())
45 31 : , indexOut_(pref.getAlsaCardout())
46 31 : , indexRing_(pref.getAlsaCardRingtone())
47 62 : , audioPlugin_(pref.getAlsaPlugin())
48 : {
49 31 : setHasNativeAEC(false);
50 31 : setHasNativeNS(false);
51 31 : }
52 :
53 62 : AlsaLayer::~AlsaLayer()
54 : {
55 31 : status_ = Status::Idle;
56 31 : stopThread();
57 :
58 : /* Then close the audio devices */
59 31 : closeCaptureStream();
60 31 : closePlaybackStream();
61 31 : closeRingtoneStream();
62 62 : }
63 :
64 : /**
65 : * Reimplementation of run()
66 : */
67 : void
68 221 : AlsaLayer::run()
69 : {
70 221 : if (playbackHandle_)
71 0 : playbackChanged(true);
72 221 : if (captureHandle_)
73 0 : recordChanged(true);
74 :
75 22033135238 : while (status_ == Status::Started and running_) {
76 22033135017 : playback();
77 22033135017 : ringtone();
78 22033135017 : capture();
79 : }
80 :
81 221 : playbackChanged(false);
82 221 : recordChanged(false);
83 221 : }
84 :
85 : // Retry approach taken from pa_linux_alsa.c, part of PortAudio
86 : bool
87 162 : AlsaLayer::openDevice(snd_pcm_t** pcm,
88 : const std::string& dev,
89 : snd_pcm_stream_t stream,
90 : AudioFormat& format)
91 : {
92 162 : JAMI_DBG("Alsa: Opening %s device '%s'",
93 : (stream == SND_PCM_STREAM_CAPTURE) ? "capture" : "playback",
94 : dev.c_str());
95 :
96 : static const int MAX_RETRIES = 10; // times of 100ms
97 162 : int err, tries = 0;
98 : do {
99 162 : err = snd_pcm_open(pcm, dev.c_str(), stream, 0);
100 : // Retry if busy, since dmix plugin may not have released the device yet
101 162 : if (err == -EBUSY) {
102 : // We're called in audioThread_ context, so if exit is requested
103 : // force return now
104 0 : std::this_thread::sleep_for(std::chrono::milliseconds(100));
105 : }
106 162 : } while (err == -EBUSY and ++tries <= MAX_RETRIES);
107 :
108 162 : if (err < 0) {
109 162 : JAMI_ERR("Alsa: couldn't open %s device %s : %s",
110 : (stream == SND_PCM_STREAM_CAPTURE) ? "capture"
111 : : (stream == SND_PCM_STREAM_PLAYBACK) ? "playback"
112 : : "ringtone",
113 : dev.c_str(),
114 : snd_strerror(err));
115 162 : return false;
116 : }
117 :
118 0 : if (!alsa_set_params(*pcm, format)) {
119 0 : snd_pcm_close(*pcm);
120 0 : return false;
121 : }
122 :
123 0 : return true;
124 : }
125 :
126 : void
127 221 : AlsaLayer::startStream(AudioDeviceType type)
128 : {
129 221 : std::unique_lock lk(mutex_);
130 221 : status_ = Status::Starting;
131 221 : stopThread();
132 :
133 221 : bool dsnop = audioPlugin_ == PCM_DMIX_DSNOOP;
134 :
135 221 : if (type == AudioDeviceType::PLAYBACK and not is_playback_open_) {
136 109 : is_playback_open_ = openDevice(&playbackHandle_,
137 218 : buildDeviceTopo(dsnop ? PCM_DMIX : audioPlugin_, indexOut_),
138 : SND_PCM_STREAM_PLAYBACK,
139 109 : audioFormat_);
140 109 : if (not is_playback_open_)
141 109 : emitSignal<libjami::ConfigurationSignal::Error>(ALSA_PLAYBACK_DEVICE);
142 :
143 109 : hardwareFormatAvailable(getFormat());
144 109 : startPlaybackStream();
145 : }
146 :
147 59 : if (type == AudioDeviceType::RINGTONE and getIndexPlayback() != getIndexRingtone()
148 280 : and not ringtoneHandle_) {
149 0 : if (!openDevice(&ringtoneHandle_,
150 0 : buildDeviceTopo(dsnop ? PCM_DMIX : audioPlugin_, indexRing_),
151 : SND_PCM_STREAM_PLAYBACK,
152 0 : audioFormat_))
153 0 : emitSignal<libjami::ConfigurationSignal::Error>(ALSA_PLAYBACK_DEVICE);
154 : }
155 :
156 221 : if (type == AudioDeviceType::CAPTURE and not is_capture_open_) {
157 53 : is_capture_open_ = openDevice(&captureHandle_,
158 106 : buildDeviceTopo(dsnop ? PCM_DSNOOP : audioPlugin_, indexIn_),
159 : SND_PCM_STREAM_CAPTURE,
160 53 : audioInputFormat_);
161 :
162 53 : if (not is_capture_open_)
163 53 : emitSignal<libjami::ConfigurationSignal::Error>(ALSA_CAPTURE_DEVICE);
164 53 : prepareCaptureStream();
165 53 : startCaptureStream();
166 : }
167 :
168 221 : status_ = Status::Started;
169 221 : startThread();
170 221 : }
171 :
172 : void
173 221 : AlsaLayer::stopStream(AudioDeviceType stream)
174 : {
175 221 : std::unique_lock lk(mutex_);
176 221 : stopThread();
177 :
178 221 : if (stream == AudioDeviceType::CAPTURE && is_capture_open_) {
179 0 : closeCaptureStream();
180 : }
181 :
182 221 : if (stream == AudioDeviceType::PLAYBACK && is_playback_open_) {
183 0 : closePlaybackStream();
184 0 : flushUrgent();
185 0 : flushMain();
186 : }
187 :
188 221 : if (stream == AudioDeviceType::RINGTONE and ringtoneHandle_) {
189 0 : closeRingtoneStream();
190 : }
191 :
192 221 : if (is_capture_open_ or is_playback_open_ or ringtoneHandle_) {
193 0 : startThread();
194 : } else {
195 221 : status_ = Status::Idle;
196 : }
197 221 : }
198 :
199 : void
200 221 : AlsaLayer::startThread()
201 : {
202 221 : running_ = true;
203 221 : audioThread_ = std::thread(&AlsaLayer::run, this);
204 221 : }
205 :
206 : void
207 473 : AlsaLayer::stopThread()
208 : {
209 473 : running_ = false;
210 473 : if (audioThread_.joinable())
211 221 : audioThread_.join();
212 473 : }
213 :
214 : /*
215 : * GCC extension : statement expression
216 : *
217 : * ALSA_CALL(function_call, error_string) will:
218 : * call the function
219 : * display an error if the function failed
220 : * return the function return value
221 : */
222 : #define ALSA_CALL(call, error) \
223 : ({ \
224 : int err_code = call; \
225 : if (err_code < 0) \
226 : JAMI_ERR(error ": %s", snd_strerror(err_code)); \
227 : err_code; \
228 : })
229 :
230 : void
231 0 : AlsaLayer::stopCaptureStream()
232 : {
233 0 : if (captureHandle_ && ALSA_CALL(snd_pcm_drop(captureHandle_), "couldn't stop capture") >= 0) {
234 0 : is_capture_running_ = false;
235 0 : is_capture_prepared_ = false;
236 : }
237 0 : }
238 :
239 : void
240 31 : AlsaLayer::closeCaptureStream()
241 : {
242 31 : if (is_capture_prepared_ and is_capture_running_)
243 0 : stopCaptureStream();
244 :
245 31 : JAMI_DBG("Alsa: Closing capture stream");
246 62 : if (is_capture_open_
247 31 : && ALSA_CALL(snd_pcm_close(captureHandle_), "Couldn't close capture") >= 0) {
248 0 : is_capture_open_ = false;
249 0 : captureHandle_ = nullptr;
250 : }
251 31 : }
252 :
253 : void
254 53 : AlsaLayer::startCaptureStream()
255 : {
256 53 : if (captureHandle_ and not is_capture_running_)
257 0 : if (ALSA_CALL(snd_pcm_start(captureHandle_), "Couldn't start capture") >= 0)
258 0 : is_capture_running_ = true;
259 53 : }
260 :
261 : void
262 13 : AlsaLayer::stopPlaybackStream()
263 : {
264 13 : if (playbackHandle_ and is_playback_running_) {
265 0 : if (ALSA_CALL(snd_pcm_drop(playbackHandle_), "Couldn't stop playback") >= 0) {
266 0 : is_playback_running_ = false;
267 : }
268 : }
269 13 : }
270 :
271 : void
272 31 : AlsaLayer::closePlaybackStream()
273 : {
274 31 : if (is_playback_running_)
275 13 : stopPlaybackStream();
276 :
277 31 : if (is_playback_open_) {
278 0 : JAMI_DBG("Alsa: Closing playback stream");
279 0 : if (ALSA_CALL(snd_pcm_close(playbackHandle_), "Coulnd't close playback") >= 0)
280 0 : is_playback_open_ = false;
281 0 : playbackHandle_ = nullptr;
282 : }
283 31 : }
284 :
285 : void
286 31 : AlsaLayer::closeRingtoneStream()
287 : {
288 31 : if (ringtoneHandle_) {
289 0 : ALSA_CALL(snd_pcm_drop(ringtoneHandle_), "Couldn't stop ringtone");
290 0 : ALSA_CALL(snd_pcm_close(ringtoneHandle_), "Couldn't close ringtone");
291 0 : ringtoneHandle_ = nullptr;
292 : }
293 31 : }
294 :
295 : void
296 109 : AlsaLayer::startPlaybackStream()
297 : {
298 109 : is_playback_running_ = true;
299 109 : }
300 :
301 : void
302 53 : AlsaLayer::prepareCaptureStream()
303 : {
304 53 : if (is_capture_open_ and not is_capture_prepared_)
305 0 : if (ALSA_CALL(snd_pcm_prepare(captureHandle_), "Couldn't prepare capture") >= 0)
306 0 : is_capture_prepared_ = true;
307 53 : }
308 :
309 : bool
310 0 : AlsaLayer::alsa_set_params(snd_pcm_t* pcm_handle, AudioFormat& format)
311 : {
312 : #define TRY(call, error) \
313 : do { \
314 : if (ALSA_CALL(call, error) < 0) \
315 : return false; \
316 : } while (0)
317 :
318 : snd_pcm_hw_params_t* hwparams;
319 0 : snd_pcm_hw_params_alloca(&hwparams);
320 :
321 0 : const unsigned RING_ALSA_PERIOD_SIZE = 160;
322 0 : const unsigned RING_ALSA_NB_PERIOD = 8;
323 0 : const unsigned RING_ALSA_BUFFER_SIZE = RING_ALSA_PERIOD_SIZE * RING_ALSA_NB_PERIOD;
324 :
325 0 : snd_pcm_uframes_t period_size = RING_ALSA_PERIOD_SIZE;
326 0 : snd_pcm_uframes_t buffer_size = RING_ALSA_BUFFER_SIZE;
327 0 : unsigned int periods = RING_ALSA_NB_PERIOD;
328 :
329 0 : snd_pcm_uframes_t period_size_min = 0;
330 0 : snd_pcm_uframes_t period_size_max = 0;
331 0 : snd_pcm_uframes_t buffer_size_min = 0;
332 0 : snd_pcm_uframes_t buffer_size_max = 0;
333 :
334 : #define HW pcm_handle, hwparams /* hardware parameters */
335 0 : TRY(snd_pcm_hw_params_any(HW), "hwparams init");
336 :
337 0 : TRY(snd_pcm_hw_params_set_access(HW, SND_PCM_ACCESS_RW_INTERLEAVED), "access type");
338 0 : TRY(snd_pcm_hw_params_set_format(HW, SND_PCM_FORMAT_S16_LE), "sample format");
339 :
340 0 : TRY(snd_pcm_hw_params_set_rate_resample(HW, 0),
341 : "hardware sample rate"); /* prevent software resampling */
342 0 : TRY(snd_pcm_hw_params_set_rate_near(HW, &format.sample_rate, nullptr), "sample rate");
343 :
344 : // TODO: use snd_pcm_query_chmaps or similar to get hardware channel num
345 0 : audioFormat_.nb_channels = 2;
346 0 : format.nb_channels = 2;
347 0 : TRY(snd_pcm_hw_params_set_channels_near(HW, &format.nb_channels), "channel count");
348 :
349 0 : snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
350 0 : snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
351 0 : snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, nullptr);
352 0 : snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, nullptr);
353 0 : JAMI_DBG("Buffer size range from %lu to %lu", buffer_size_min, buffer_size_max);
354 0 : JAMI_DBG("Period size range from %lu to %lu", period_size_min, period_size_max);
355 0 : buffer_size = buffer_size > buffer_size_max ? buffer_size_max : buffer_size;
356 0 : buffer_size = buffer_size < buffer_size_min ? buffer_size_min : buffer_size;
357 0 : period_size = period_size > period_size_max ? period_size_max : period_size;
358 0 : period_size = period_size < period_size_min ? period_size_min : period_size;
359 :
360 0 : TRY(snd_pcm_hw_params_set_buffer_size_near(HW, &buffer_size),
361 : "Unable to set buffer size for playback");
362 0 : TRY(snd_pcm_hw_params_set_period_size_near(HW, &period_size, nullptr),
363 : "Unable to set period size for playback");
364 0 : TRY(snd_pcm_hw_params_set_periods_near(HW, &periods, nullptr),
365 : "Unable to set number of periods for playback");
366 0 : TRY(snd_pcm_hw_params(HW), "hwparams");
367 :
368 0 : snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
369 0 : snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr);
370 0 : snd_pcm_hw_params_get_rate(hwparams, &format.sample_rate, nullptr);
371 0 : snd_pcm_hw_params_get_channels(hwparams, &format.nb_channels);
372 0 : JAMI_DBG("Was set period_size = %lu", period_size);
373 0 : JAMI_DBG("Was set buffer_size = %lu", buffer_size);
374 :
375 0 : if (2 * period_size > buffer_size) {
376 0 : JAMI_ERR("buffer to small, could not use");
377 0 : return false;
378 : }
379 :
380 : #undef HW
381 :
382 0 : JAMI_DBG("%s using format %s",
383 : (snd_pcm_stream(pcm_handle) == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
384 : format.toString().c_str());
385 :
386 0 : snd_pcm_sw_params_t* swparams = nullptr;
387 0 : snd_pcm_sw_params_alloca(&swparams);
388 :
389 : #define SW pcm_handle, swparams /* software parameters */
390 0 : snd_pcm_sw_params_current(SW);
391 0 : TRY(snd_pcm_sw_params_set_start_threshold(SW, period_size * 2), "start threshold");
392 0 : TRY(snd_pcm_sw_params(SW), "sw parameters");
393 : #undef SW
394 :
395 0 : return true;
396 :
397 : #undef TRY
398 : }
399 :
400 : // TODO first frame causes broken pipe (underrun) because not enough data is sent
401 : // we should wait until the handle is ready
402 : void
403 0 : AlsaLayer::write(const AudioFrame& buffer, snd_pcm_t* handle)
404 : {
405 0 : int err = snd_pcm_writei(handle,
406 0 : (const void*) buffer.pointer()->data[0],
407 0 : buffer.pointer()->nb_samples);
408 :
409 0 : if (err < 0)
410 0 : snd_pcm_recover(handle, err, 0);
411 :
412 0 : if (err >= 0)
413 0 : return;
414 :
415 0 : switch (err) {
416 0 : case -EPIPE:
417 : case -ESTRPIPE:
418 : case -EIO: {
419 : snd_pcm_status_t* status;
420 0 : snd_pcm_status_alloca(&status);
421 :
422 0 : if (ALSA_CALL(snd_pcm_status(handle, status), "Cannot get playback handle status") >= 0)
423 0 : if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
424 0 : stopPlaybackStream();
425 0 : startPlaybackStream();
426 : }
427 :
428 0 : ALSA_CALL(snd_pcm_writei(handle,
429 : (const void*) buffer.pointer()->data[0],
430 : buffer.pointer()->nb_samples),
431 : "XRUN handling failed");
432 0 : break;
433 : }
434 :
435 0 : case -EBADFD: {
436 : snd_pcm_status_t* status;
437 0 : snd_pcm_status_alloca(&status);
438 :
439 0 : if (ALSA_CALL(snd_pcm_status(handle, status), "Cannot get playback handle status") >= 0) {
440 0 : if (snd_pcm_status_get_state(status) == SND_PCM_STATE_SETUP) {
441 0 : JAMI_ERR("Writing in state SND_PCM_STATE_SETUP, should be "
442 : "SND_PCM_STATE_PREPARED or SND_PCM_STATE_RUNNING");
443 0 : int error = snd_pcm_prepare(handle);
444 :
445 0 : if (error < 0) {
446 0 : JAMI_ERR("Failed to prepare handle: %s", snd_strerror(error));
447 0 : stopPlaybackStream();
448 : }
449 : }
450 : }
451 :
452 0 : break;
453 : }
454 :
455 0 : default:
456 0 : JAMI_ERR("Unknown write error, dropping frames: %s", snd_strerror(err));
457 0 : stopPlaybackStream();
458 0 : break;
459 : }
460 : }
461 :
462 : std::unique_ptr<AudioFrame>
463 0 : AlsaLayer::read(unsigned frames)
464 : {
465 0 : if (snd_pcm_state(captureHandle_) == SND_PCM_STATE_XRUN) {
466 0 : prepareCaptureStream();
467 0 : startCaptureStream();
468 : }
469 :
470 0 : auto ret = std::make_unique<AudioFrame>(audioInputFormat_, frames);
471 0 : int err = snd_pcm_readi(captureHandle_, ret->pointer()->data[0], frames);
472 :
473 0 : if (err >= 0) {
474 0 : ret->pointer()->nb_samples = err;
475 0 : return ret;
476 : }
477 :
478 0 : switch (err) {
479 0 : case -EPIPE:
480 : case -ESTRPIPE:
481 : case -EIO: {
482 : snd_pcm_status_t* status;
483 0 : snd_pcm_status_alloca(&status);
484 :
485 0 : if (ALSA_CALL(snd_pcm_status(captureHandle_, status), "Get status failed") >= 0)
486 0 : if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
487 0 : stopCaptureStream();
488 0 : prepareCaptureStream();
489 0 : startCaptureStream();
490 : }
491 :
492 0 : JAMI_ERR("XRUN capture ignored (%s)", snd_strerror(err));
493 0 : break;
494 : }
495 :
496 0 : case -EPERM:
497 0 : JAMI_ERR("Can't capture, EPERM (%s)", snd_strerror(err));
498 0 : prepareCaptureStream();
499 0 : startCaptureStream();
500 0 : break;
501 : }
502 :
503 0 : return {};
504 0 : }
505 :
506 : std::string
507 162 : AlsaLayer::buildDeviceTopo(const std::string& plugin, int card)
508 : {
509 162 : if (plugin == PCM_DEFAULT)
510 162 : return plugin;
511 :
512 0 : return fmt::format("{}:{}", plugin, card);
513 : }
514 :
515 : static bool
516 0 : safeUpdate(snd_pcm_t* handle, long& samples)
517 : {
518 0 : samples = snd_pcm_avail_update(handle);
519 :
520 0 : if (samples < 0) {
521 0 : samples = snd_pcm_recover(handle, samples, 0);
522 :
523 0 : if (samples < 0) {
524 0 : JAMI_ERR("Got unrecoverable error from snd_pcm_avail_update: %s", snd_strerror(samples));
525 0 : return false;
526 : }
527 : }
528 :
529 0 : return true;
530 : }
531 :
532 : static std::vector<std::string>
533 0 : getValues(const std::vector<HwIDPair>& deviceMap)
534 : {
535 0 : std::vector<std::string> audioDeviceList;
536 0 : audioDeviceList.reserve(deviceMap.size());
537 :
538 0 : for (const auto& dev : deviceMap)
539 0 : audioDeviceList.push_back(dev.second);
540 :
541 0 : return audioDeviceList;
542 0 : }
543 :
544 : std::vector<std::string>
545 0 : AlsaLayer::getCaptureDeviceList() const
546 : {
547 0 : return getValues(getAudioDeviceIndexMap(true));
548 : }
549 :
550 : std::vector<std::string>
551 0 : AlsaLayer::getPlaybackDeviceList() const
552 : {
553 0 : return getValues(getAudioDeviceIndexMap(false));
554 : }
555 :
556 : std::vector<HwIDPair>
557 0 : AlsaLayer::getAudioDeviceIndexMap(bool getCapture) const
558 : {
559 : snd_ctl_t* handle;
560 : snd_ctl_card_info_t* info;
561 : snd_pcm_info_t* pcminfo;
562 0 : snd_ctl_card_info_alloca(&info);
563 0 : snd_pcm_info_alloca(&pcminfo);
564 :
565 0 : int numCard = -1;
566 :
567 0 : std::vector<HwIDPair> audioDevice;
568 :
569 0 : if (snd_card_next(&numCard) < 0 || numCard < 0)
570 0 : return audioDevice;
571 :
572 : do {
573 0 : std::string name = fmt::format("hw:{}", numCard);
574 :
575 0 : if (snd_ctl_open(&handle, name.c_str(), 0) == 0) {
576 0 : if (snd_ctl_card_info(handle, info) == 0) {
577 0 : snd_pcm_info_set_device(pcminfo, 0);
578 0 : snd_pcm_info_set_stream(pcminfo,
579 : getCapture ? SND_PCM_STREAM_CAPTURE
580 : : SND_PCM_STREAM_PLAYBACK);
581 :
582 : int err;
583 0 : if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
584 0 : JAMI_WARN("Cannot get info for %s %s: %s",
585 : getCapture ? "capture device" : "playback device",
586 : name.c_str(),
587 : snd_strerror(err));
588 : } else {
589 0 : JAMI_DBG("card %i : %s [%s]",
590 : numCard,
591 : snd_ctl_card_info_get_id(info),
592 : snd_ctl_card_info_get_name(info));
593 0 : std::string description = snd_ctl_card_info_get_name(info);
594 0 : description.append(" - ");
595 0 : description.append(snd_pcm_info_get_name(pcminfo));
596 :
597 : // The number of the sound card is associated with a string description
598 0 : audioDevice.emplace_back(numCard, std::move(description));
599 0 : }
600 : }
601 :
602 0 : snd_ctl_close(handle);
603 : }
604 0 : } while (snd_card_next(&numCard) >= 0 && numCard >= 0);
605 :
606 0 : return audioDevice;
607 0 : }
608 :
609 : bool
610 93 : AlsaLayer::soundCardIndexExists(int card, AudioDeviceType stream)
611 : {
612 0 : std::string name = fmt::format("hw:{}", card);
613 :
614 : snd_ctl_t* handle;
615 93 : if (snd_ctl_open(&handle, name.c_str(), 0) != 0)
616 93 : return false;
617 :
618 : snd_pcm_info_t* pcminfo;
619 0 : snd_pcm_info_alloca(&pcminfo);
620 0 : snd_pcm_info_set_stream(pcminfo,
621 0 : stream == AudioDeviceType::PLAYBACK ? SND_PCM_STREAM_PLAYBACK
622 : : SND_PCM_STREAM_CAPTURE);
623 0 : bool ret = snd_ctl_pcm_info(handle, pcminfo) >= 0;
624 0 : snd_ctl_close(handle);
625 0 : return ret;
626 93 : }
627 :
628 : int
629 0 : AlsaLayer::getAudioDeviceIndex(const std::string& description, AudioDeviceType type) const
630 : {
631 0 : std::vector<HwIDPair> devices = getAudioDeviceIndexMap(type == AudioDeviceType::CAPTURE);
632 :
633 0 : for (const auto& dev : devices)
634 0 : if (dev.second == description)
635 0 : return dev.first;
636 :
637 : // else return the default one
638 0 : return 0;
639 0 : }
640 :
641 : std::string
642 0 : AlsaLayer::getAudioDeviceName(int index, AudioDeviceType type) const
643 : {
644 : // a bit ugly and wrong.. i do not know how to implement it better in alsalayer.
645 : // in addition, for now it is used in pulselayer only due to alsa and pulse layers api
646 : // differences. but after some tweaking in alsalayer, it could be used in it too.
647 0 : switch (type) {
648 0 : case AudioDeviceType::PLAYBACK:
649 : case AudioDeviceType::RINGTONE:
650 0 : return getPlaybackDeviceList().at(index);
651 :
652 0 : case AudioDeviceType::CAPTURE:
653 0 : return getCaptureDeviceList().at(index);
654 0 : default:
655 : // Should never happen
656 0 : JAMI_ERR("Unexpected type");
657 0 : return "";
658 : }
659 : }
660 :
661 : void
662 22033135017 : AlsaLayer::capture()
663 : {
664 22033135017 : if (!captureHandle_ or !is_capture_running_)
665 22033135017 : return;
666 :
667 0 : snd_pcm_wait(captureHandle_, 10);
668 :
669 0 : int toGetFrames = snd_pcm_avail_update(captureHandle_);
670 0 : if (toGetFrames < 0)
671 0 : JAMI_ERR("Audio: Mic error: %s", snd_strerror(toGetFrames));
672 0 : if (toGetFrames <= 0)
673 0 : return;
674 :
675 0 : const int framesPerBufferAlsa = 2048;
676 0 : toGetFrames = std::min(framesPerBufferAlsa, toGetFrames);
677 0 : if (auto r = read(toGetFrames)) {
678 0 : putRecorded(std::move(r));
679 : } else
680 0 : JAMI_ERR("ALSA MIC : Couldn't read!");
681 : }
682 :
683 : void
684 22033135017 : AlsaLayer::playback()
685 : {
686 22033135017 : if (!playbackHandle_)
687 22033135017 : return;
688 :
689 0 : snd_pcm_wait(playbackHandle_, 20);
690 :
691 0 : long maxFrames = 0;
692 0 : if (not safeUpdate(playbackHandle_, maxFrames))
693 0 : return;
694 :
695 0 : if (auto toPlay = getToPlay(audioFormat_, maxFrames)) {
696 0 : write(*toPlay, playbackHandle_);
697 0 : }
698 : }
699 :
700 : void
701 22033135017 : AlsaLayer::ringtone()
702 : {
703 22033135017 : if (!ringtoneHandle_)
704 22033135017 : return;
705 :
706 0 : long ringtoneAvailFrames = 0;
707 0 : if (not safeUpdate(ringtoneHandle_, ringtoneAvailFrames))
708 0 : return;
709 :
710 0 : if (auto toRing = getToRing(audioFormat_, ringtoneAvailFrames)) {
711 0 : write(*toRing, ringtoneHandle_);
712 0 : }
713 : }
714 :
715 : void
716 0 : AlsaLayer::updatePreference(AudioPreference& preference, int index, AudioDeviceType type)
717 : {
718 0 : switch (type) {
719 0 : case AudioDeviceType::PLAYBACK:
720 0 : preference.setAlsaCardout(index);
721 0 : break;
722 :
723 0 : case AudioDeviceType::CAPTURE:
724 0 : preference.setAlsaCardin(index);
725 0 : break;
726 :
727 0 : case AudioDeviceType::RINGTONE:
728 0 : preference.setAlsaCardRingtone(index);
729 0 : break;
730 :
731 0 : default:
732 0 : break;
733 : }
734 0 : }
735 :
736 : } // namespace jami
|