LCOV - code coverage report
Current view: top level - src/media/audio/alsa - alsalayer.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 34.0 % 391 133
Test Date: 2026-06-13 09:18:46 Functions: 23.5 % 132 31

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

Generated by: LCOV version 2.0-1