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