LCOV - code coverage report
Current view: top level - foo/src/media/audio/alsa - alsalayer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 118 366 32.2 %
Date: 2025-12-18 10:07:43 Functions: 21 33 63.6 %

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

Generated by: LCOV version 1.14