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 375 31.5 %
Date: 2026-01-22 10:39:23 Functions: 21 33 63.6 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "alsalayer.h"
      19             : #include "logger.h"
      20             : #include "manager.h"
      21             : #include "noncopyable.h"
      22             : #include "client/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         215 : AlsaLayer::run()
      63             : {
      64         215 :     if (playbackHandle_)
      65           0 :         playbackChanged(true);
      66         215 :     if (captureHandle_)
      67           0 :         recordChanged(true);
      68             : 
      69 19044458336 :     while (status_ == Status::Started and running_) {
      70 19044458121 :         playback();
      71 19044458121 :         ringtone();
      72 19044458121 :         capture();
      73             :     }
      74             : 
      75         215 :     playbackChanged(false);
      76         215 :     recordChanged(false);
      77         215 : }
      78             : 
      79             : // Retry approach taken from pa_linux_alsa.c, part of PortAudio
      80             : bool
      81         160 : AlsaLayer::openDevice(snd_pcm_t** pcm, const std::string& dev, snd_pcm_stream_t stream, AudioFormat& format)
      82             : {
      83         160 :     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         160 :     int err, tries = 0;
      87             :     do {
      88         160 :         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         160 :         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         160 :     } while (err == -EBUSY and ++tries <= MAX_RETRIES);
      96             : 
      97         160 :     if (err < 0) {
      98         160 :         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         160 :         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         215 : AlsaLayer::startStream(AudioDeviceType type)
     117             : {
     118         215 :     std::unique_lock lk(mutex_);
     119         215 :     status_ = Status::Starting;
     120         215 :     stopThread();
     121             : 
     122         215 :     bool dsnop = audioPlugin_ == PCM_DMIX_DSNOOP;
     123             : 
     124         215 :     if (type == AudioDeviceType::PLAYBACK and not is_playback_open_) {
     125         100 :         is_playback_open_ = openDevice(&playbackHandle_,
     126         200 :                                        buildDeviceTopo(dsnop ? PCM_DMIX : audioPlugin_, indexOut_),
     127             :                                        SND_PCM_STREAM_PLAYBACK,
     128         100 :                                        audioFormat_);
     129         100 :         if (not is_playback_open_)
     130         100 :             emitSignal<libjami::ConfigurationSignal::Error>(ALSA_PLAYBACK_DEVICE);
     131             : 
     132         100 :         hardwareFormatAvailable(getFormat());
     133         100 :         startPlaybackStream();
     134             :     }
     135             : 
     136         215 :     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         215 :     if (type == AudioDeviceType::CAPTURE and not is_capture_open_) {
     145          60 :         is_capture_open_ = openDevice(&captureHandle_,
     146         120 :                                       buildDeviceTopo(dsnop ? PCM_DSNOOP : audioPlugin_, indexIn_),
     147             :                                       SND_PCM_STREAM_CAPTURE,
     148          60 :                                       audioInputFormat_);
     149             : 
     150          60 :         if (not is_capture_open_)
     151          60 :             emitSignal<libjami::ConfigurationSignal::Error>(ALSA_CAPTURE_DEVICE);
     152          60 :         prepareCaptureStream();
     153          60 :         startCaptureStream();
     154             :     }
     155             : 
     156         215 :     status_ = Status::Started;
     157         215 :     startThread();
     158         215 : }
     159             : 
     160             : void
     161         215 : AlsaLayer::stopStream(AudioDeviceType stream)
     162             : {
     163         215 :     std::unique_lock lk(mutex_);
     164         215 :     stopThread();
     165             : 
     166         215 :     if (stream == AudioDeviceType::CAPTURE && is_capture_open_) {
     167           0 :         closeCaptureStream();
     168             :     }
     169             : 
     170         215 :     if (stream == AudioDeviceType::PLAYBACK && is_playback_open_) {
     171           0 :         closePlaybackStream();
     172           0 :         flushUrgent();
     173           0 :         flushMain();
     174             :     }
     175             : 
     176         215 :     if (stream == AudioDeviceType::RINGTONE and ringtoneHandle_) {
     177           0 :         closeRingtoneStream();
     178             :     }
     179             : 
     180         215 :     if (is_capture_open_ or is_playback_open_ or ringtoneHandle_) {
     181           0 :         startThread();
     182             :     } else {
     183         215 :         status_ = Status::Idle;
     184             :     }
     185         215 : }
     186             : 
     187             : void
     188         215 : AlsaLayer::startThread()
     189             : {
     190         215 :     running_ = true;
     191         215 :     audioThread_ = std::thread(&AlsaLayer::run, this);
     192         215 : }
     193             : 
     194             : void
     195         462 : AlsaLayer::stopThread()
     196             : {
     197         462 :     running_ = false;
     198         462 :     if (audioThread_.joinable())
     199         215 :         audioThread_.join();
     200         462 : }
     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          60 : AlsaLayer::startCaptureStream()
     242             : {
     243          60 :     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          60 : }
     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         100 : AlsaLayer::startPlaybackStream()
     284             : {
     285         100 :     is_playback_running_ = true;
     286         100 : }
     287             : 
     288             : void
     289          60 : AlsaLayer::prepareCaptureStream()
     290             : {
     291          60 :     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          60 : }
     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             : 
     326           0 :     if (snd_pcm_hw_params_test_format(HW, SND_PCM_FORMAT_FLOAT_LE) == 0) {
     327           0 :         TRY(snd_pcm_hw_params_set_format(HW, SND_PCM_FORMAT_FLOAT_LE), "sample format");
     328           0 :         format = format.withSampleFormat(AV_SAMPLE_FMT_FLT);
     329             :     } else {
     330           0 :         TRY(snd_pcm_hw_params_set_format(HW, SND_PCM_FORMAT_S16_LE), "sample format");
     331           0 :         format = format.withSampleFormat(AV_SAMPLE_FMT_S16);
     332             :     }
     333             : 
     334           0 :     TRY(snd_pcm_hw_params_set_rate_resample(HW, 0), "hardware sample rate"); /* prevent software resampling */
     335           0 :     TRY(snd_pcm_hw_params_set_rate_near(HW, &format.sample_rate, nullptr), "sample rate");
     336             : 
     337             :     // Query the max number of channels supported by the hardware
     338           0 :     unsigned int max_channels = 0;
     339           0 :     if (snd_pcm_hw_params_get_channels_max(hwparams, &max_channels) < 0) {
     340           0 :         JAMI_WARN("Unable to query hardware channel number, defaulting to 2");
     341           0 :         max_channels = 2;
     342             :     }
     343             : 
     344           0 :     format.nb_channels = std::min(max_channels, 2u);
     345           0 :     if (format.nb_channels == 0)
     346           0 :         format.nb_channels = 2;
     347             : 
     348           0 :     TRY(snd_pcm_hw_params_set_channels_near(HW, &format.nb_channels), "channel count");
     349             : 
     350           0 :     snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
     351           0 :     snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
     352           0 :     snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, nullptr);
     353           0 :     snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, nullptr);
     354           0 :     JAMI_DBG("Buffer size range from %lu to %lu", buffer_size_min, buffer_size_max);
     355           0 :     JAMI_DBG("Period size range from %lu to %lu", period_size_min, period_size_max);
     356           0 :     buffer_size = buffer_size > buffer_size_max ? buffer_size_max : buffer_size;
     357           0 :     buffer_size = buffer_size < buffer_size_min ? buffer_size_min : buffer_size;
     358           0 :     period_size = period_size > period_size_max ? period_size_max : period_size;
     359           0 :     period_size = period_size < period_size_min ? period_size_min : period_size;
     360             : 
     361           0 :     TRY(snd_pcm_hw_params_set_buffer_size_near(HW, &buffer_size), "Unable to set buffer size for playback");
     362           0 :     TRY(snd_pcm_hw_params_set_period_size_near(HW, &period_size, nullptr), "Unable to set period size for playback");
     363           0 :     TRY(snd_pcm_hw_params_set_periods_near(HW, &periods, nullptr), "Unable to set number of periods for playback");
     364           0 :     TRY(snd_pcm_hw_params(HW), "hwparams");
     365             : 
     366           0 :     snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
     367           0 :     snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr);
     368           0 :     snd_pcm_hw_params_get_rate(hwparams, &format.sample_rate, nullptr);
     369           0 :     snd_pcm_hw_params_get_channels(hwparams, &format.nb_channels);
     370           0 :     JAMI_DBG("Was set period_size = %lu", period_size);
     371           0 :     JAMI_DBG("Was set buffer_size = %lu", buffer_size);
     372             : 
     373           0 :     if (2 * period_size > buffer_size) {
     374           0 :         JAMI_ERR("buffer to small, unable to use");
     375           0 :         return false;
     376             :     }
     377             : 
     378             : #undef HW
     379             : 
     380           0 :     JAMI_DBG("%s using format %s",
     381             :              (snd_pcm_stream(pcm_handle) == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
     382             :              format.toString().c_str());
     383             : 
     384           0 :     snd_pcm_sw_params_t* swparams = nullptr;
     385           0 :     snd_pcm_sw_params_alloca(&swparams);
     386             : 
     387             : #define SW pcm_handle, swparams /* software parameters */
     388           0 :     snd_pcm_sw_params_current(SW);
     389           0 :     TRY(snd_pcm_sw_params_set_start_threshold(SW, period_size * 2), "start threshold");
     390           0 :     TRY(snd_pcm_sw_params(SW), "sw parameters");
     391             : #undef SW
     392             : 
     393           0 :     return true;
     394             : 
     395             : #undef TRY
     396             : }
     397             : 
     398             : // TODO first frame causes broken pipe (underrun) because not enough data is sent
     399             : // we should wait until the handle is ready
     400             : void
     401           0 : AlsaLayer::write(const AudioFrame& buffer, snd_pcm_t* handle)
     402             : {
     403           0 :     int err = snd_pcm_writei(handle, (const void*) buffer.pointer()->data[0], buffer.pointer()->nb_samples);
     404             : 
     405           0 :     if (err < 0)
     406           0 :         snd_pcm_recover(handle, err, 0);
     407             : 
     408           0 :     if (err >= 0)
     409           0 :         return;
     410             : 
     411           0 :     switch (err) {
     412           0 :     case -EPIPE:
     413             :     case -ESTRPIPE:
     414             :     case -EIO: {
     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_XRUN) {
     420           0 :                 stopPlaybackStream();
     421           0 :                 startPlaybackStream();
     422             :             }
     423             : 
     424           0 :         ALSA_CALL(snd_pcm_writei(handle, (const void*) buffer.pointer()->data[0], 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         160 : AlsaLayer::buildDeviceTopo(const std::string& plugin, int card)
     502             : {
     503         160 :     if (plugin == PCM_DEFAULT)
     504         160 :         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, getCapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
     573             : 
     574             :                 int err;
     575           0 :                 if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
     576           0 :                     JAMI_WARN("Unable to get info for %s %s: %s",
     577             :                               getCapture ? "capture device" : "playback device",
     578             :                               name.c_str(),
     579             :                               snd_strerror(err));
     580             :                 } else {
     581           0 :                     JAMI_DBG("card %i : %s [%s]",
     582             :                              numCard,
     583             :                              snd_ctl_card_info_get_id(info),
     584             :                              snd_ctl_card_info_get_name(info));
     585           0 :                     std::string description = snd_ctl_card_info_get_name(info);
     586           0 :                     description.append(" - ");
     587           0 :                     description.append(snd_pcm_info_get_name(pcminfo));
     588             : 
     589             :                     // The number of the sound card is associated with a string description
     590           0 :                     audioDevice.emplace_back(numCard, std::move(description));
     591           0 :                 }
     592             :             }
     593             : 
     594           0 :             snd_ctl_close(handle);
     595             :         }
     596           0 :     } while (snd_card_next(&numCard) >= 0 && numCard >= 0);
     597             : 
     598           0 :     return audioDevice;
     599           0 : }
     600             : 
     601             : bool
     602          96 : AlsaLayer::soundCardIndexExists(int card, AudioDeviceType stream)
     603             : {
     604           0 :     std::string name = fmt::format("hw:{}", card);
     605             : 
     606             :     snd_ctl_t* handle;
     607          96 :     if (snd_ctl_open(&handle, name.c_str(), 0) != 0)
     608          96 :         return false;
     609             : 
     610             :     snd_pcm_info_t* pcminfo;
     611           0 :     snd_pcm_info_alloca(&pcminfo);
     612           0 :     snd_pcm_info_set_stream(pcminfo,
     613           0 :                             stream == AudioDeviceType::PLAYBACK ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE);
     614           0 :     bool ret = snd_ctl_pcm_info(handle, pcminfo) >= 0;
     615           0 :     snd_ctl_close(handle);
     616           0 :     return ret;
     617          96 : }
     618             : 
     619             : int
     620           0 : AlsaLayer::getAudioDeviceIndex(const std::string& description, AudioDeviceType type) const
     621             : {
     622           0 :     std::vector<HwIDPair> devices = getAudioDeviceIndexMap(type == AudioDeviceType::CAPTURE);
     623             : 
     624           0 :     for (const auto& dev : devices)
     625           0 :         if (dev.second == description)
     626           0 :             return dev.first;
     627             : 
     628             :     // else return the default one
     629           0 :     return 0;
     630           0 : }
     631             : 
     632             : std::string
     633           0 : AlsaLayer::getAudioDeviceName(int index, AudioDeviceType type) const
     634             : {
     635             :     // a bit ugly and wrong.. i do not know how to implement it better in alsalayer.
     636             :     // in addition, for now it is used in pulselayer only due to alsa and pulse layers api
     637             :     // differences. but after some tweaking in alsalayer, it could be used in it too.
     638           0 :     switch (type) {
     639           0 :     case AudioDeviceType::PLAYBACK:
     640             :     case AudioDeviceType::RINGTONE:
     641           0 :         return getPlaybackDeviceList().at(index);
     642             : 
     643           0 :     case AudioDeviceType::CAPTURE:
     644           0 :         return getCaptureDeviceList().at(index);
     645           0 :     default:
     646             :         // Should never happen
     647           0 :         JAMI_ERR("Unexpected type");
     648           0 :         return "";
     649             :     }
     650             : }
     651             : 
     652             : void
     653 19044458121 : AlsaLayer::capture()
     654             : {
     655 19044458121 :     if (!captureHandle_ or !is_capture_running_)
     656 19044458121 :         return;
     657             : 
     658           0 :     snd_pcm_wait(captureHandle_, 10);
     659             : 
     660           0 :     int toGetFrames = snd_pcm_avail_update(captureHandle_);
     661           0 :     if (toGetFrames < 0)
     662           0 :         JAMI_ERR("Audio: Mic error: %s", snd_strerror(toGetFrames));
     663           0 :     if (toGetFrames <= 0)
     664           0 :         return;
     665             : 
     666           0 :     const int framesPerBufferAlsa = 2048;
     667           0 :     toGetFrames = std::min(framesPerBufferAlsa, toGetFrames);
     668           0 :     if (auto r = read(toGetFrames)) {
     669           0 :         putRecorded(std::move(r));
     670             :     } else
     671           0 :         JAMI_ERR("ALSA MIC : Unable to read!");
     672             : }
     673             : 
     674             : void
     675 19044458121 : AlsaLayer::playback()
     676             : {
     677 19044458121 :     if (!playbackHandle_)
     678 19044458121 :         return;
     679             : 
     680           0 :     snd_pcm_wait(playbackHandle_, 20);
     681             : 
     682           0 :     long maxFrames = 0;
     683           0 :     if (not safeUpdate(playbackHandle_, maxFrames))
     684           0 :         return;
     685             : 
     686           0 :     if (auto toPlay = getToPlay(audioFormat_, maxFrames)) {
     687           0 :         write(*toPlay, playbackHandle_);
     688           0 :     }
     689             : }
     690             : 
     691             : void
     692 19044458121 : AlsaLayer::ringtone()
     693             : {
     694 19044458121 :     if (!ringtoneHandle_)
     695 19044458121 :         return;
     696             : 
     697           0 :     long ringtoneAvailFrames = 0;
     698           0 :     if (not safeUpdate(ringtoneHandle_, ringtoneAvailFrames))
     699           0 :         return;
     700             : 
     701           0 :     if (auto toRing = getToRing(audioFormat_, ringtoneAvailFrames)) {
     702           0 :         write(*toRing, ringtoneHandle_);
     703           0 :     }
     704             : }
     705             : 
     706             : void
     707           0 : AlsaLayer::updatePreference(AudioPreference& preference, int index, AudioDeviceType type)
     708             : {
     709           0 :     switch (type) {
     710           0 :     case AudioDeviceType::PLAYBACK:
     711           0 :         preference.setAlsaCardout(index);
     712           0 :         break;
     713             : 
     714           0 :     case AudioDeviceType::CAPTURE:
     715           0 :         preference.setAlsaCardin(index);
     716           0 :         break;
     717             : 
     718           0 :     case AudioDeviceType::RINGTONE:
     719           0 :         preference.setAlsaCardRingtone(index);
     720           0 :         break;
     721             : 
     722           0 :     default:
     723           0 :         break;
     724             :     }
     725           0 : }
     726             : 
     727             : } // namespace jami

Generated by: LCOV version 1.14