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