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-12-21 08:56:24 Functions: 21 33 63.6 %

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

Generated by: LCOV version 1.14