LCOV - code coverage report
Current view: top level - src/media/audio/alsa - alsalayer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 120 370 32.4 %
Date: 2024-04-26 09:41:19 Functions: 21 33 63.6 %

          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

Generated by: LCOV version 1.14