/*
 *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

#include "alsalayer.h"
#include "logger.h"
#include "manager.h"
#include "noncopyable.h"
#include "client/jami_signal.h"
#include "audio/ringbufferpool.h"
#include "audio/ringbuffer.h"
#include "audio/audioloop.h"
#include "libav_utils.h"

#include <fmt/core.h>

#include <thread>
#include <atomic>
#include <chrono>

namespace jami {

AlsaLayer::AlsaLayer(const AudioPreference& pref)
    : AudioLayer(pref)
    , indexIn_(pref.getAlsaCardin())
    , indexOut_(pref.getAlsaCardout())
    , indexRing_(pref.getAlsaCardRingtone())
    , audioPlugin_(pref.getAlsaPlugin())
{
    setHasNativeAEC(false);
    setHasNativeNS(false);
}

AlsaLayer::~AlsaLayer()
{
    status_ = Status::Idle;
    stopThread();

    /* Then close the audio devices */
    closeCaptureStream();
    closePlaybackStream();
    closeRingtoneStream();
}

/**
 * Reimplementation of run()
 */
void
AlsaLayer::run()
{
    if (playbackHandle_)
        playbackChanged(true);
    if (captureHandle_)
        recordChanged(true);

    while (status_ == Status::Started and running_) {
        playback();
        ringtone();
        capture();
    }

    playbackChanged(false);
    recordChanged(false);
}

// Retry approach taken from pa_linux_alsa.c, part of PortAudio
bool
AlsaLayer::openDevice(snd_pcm_t** pcm, const std::string& dev, snd_pcm_stream_t stream, AudioFormat& format)
{
    JAMI_DEBUG("Alsa: Opening {} device '{}' with format {}",
               (stream == SND_PCM_STREAM_CAPTURE) ? "capture" : "playback",
               dev,
               format.toString());

    static const int MAX_RETRIES = 10; // times of 100ms
    int err, tries = 0;
    do {
        err = snd_pcm_open(pcm, dev.c_str(), stream, 0);
        // Retry if busy, since dmix plugin may not have released the device yet
        if (err == -EBUSY) {
            // We're called in audioThread_ context, so if exit is requested
            // force return now
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    } while (err == -EBUSY and ++tries <= MAX_RETRIES);

    if (err < 0) {
        JAMI_ERROR("Alsa: Unable to open {} device {} : {}",
                   (stream == SND_PCM_STREAM_CAPTURE)    ? "capture"
                   : (stream == SND_PCM_STREAM_PLAYBACK) ? "playback"
                                                         : "ringtone",
                   dev,
                   snd_strerror(err));
        return false;
    }

    if (!alsa_set_params(*pcm, format)) {
        snd_pcm_close(*pcm);
        return false;
    }

    return true;
}

void
AlsaLayer::startStream(AudioDeviceType type)
{
    std::unique_lock lk(mutex_);
    status_ = Status::Starting;
    stopThread();

    bool dsnop = audioPlugin_ == PCM_DMIX_DSNOOP;

    if (type == AudioDeviceType::PLAYBACK and not is_playback_open_) {
        is_playback_open_ = openDevice(&playbackHandle_,
                                       buildDeviceTopo(dsnop ? PCM_DMIX : audioPlugin_, indexOut_),
                                       SND_PCM_STREAM_PLAYBACK,
                                       audioFormat_);
        if (not is_playback_open_)
            emitSignal<libjami::ConfigurationSignal::Error>(ALSA_PLAYBACK_DEVICE);

        hardwareFormatAvailable(getFormat());
        startPlaybackStream();
    }

    if (type == AudioDeviceType::RINGTONE and getIndexPlayback() != getIndexRingtone() and not ringtoneHandle_) {
        if (!openDevice(&ringtoneHandle_,
                        buildDeviceTopo(dsnop ? PCM_DMIX : audioPlugin_, indexRing_),
                        SND_PCM_STREAM_PLAYBACK,
                        audioFormat_))
            emitSignal<libjami::ConfigurationSignal::Error>(ALSA_PLAYBACK_DEVICE);
    }

    if (type == AudioDeviceType::CAPTURE and not is_capture_open_) {
        is_capture_open_ = openDevice(&captureHandle_,
                                      buildDeviceTopo(dsnop ? PCM_DSNOOP : audioPlugin_, indexIn_),
                                      SND_PCM_STREAM_CAPTURE,
                                      audioInputFormat_);

        if (not is_capture_open_)
            emitSignal<libjami::ConfigurationSignal::Error>(ALSA_CAPTURE_DEVICE);
        prepareCaptureStream();
        startCaptureStream();
    }

    status_ = Status::Started;
    startThread();
}

void
AlsaLayer::stopStream(AudioDeviceType stream)
{
    std::unique_lock lk(mutex_);
    stopThread();

    if (stream == AudioDeviceType::CAPTURE && is_capture_open_) {
        closeCaptureStream();
    }

    if (stream == AudioDeviceType::PLAYBACK && is_playback_open_) {
        closePlaybackStream();
        flushUrgent();
        flushMain();
    }

    if (stream == AudioDeviceType::RINGTONE and ringtoneHandle_) {
        closeRingtoneStream();
    }

    if (is_capture_open_ or is_playback_open_ or ringtoneHandle_) {
        startThread();
    } else {
        status_ = Status::Idle;
    }
}

void
AlsaLayer::startThread()
{
    running_ = true;
    audioThread_ = std::thread(&AlsaLayer::run, this);
}

void
AlsaLayer::stopThread()
{
    running_ = false;
    if (audioThread_.joinable())
        audioThread_.join();
}

/*
 * GCC extension : statement expression
 *
 * ALSA_CALL(function_call, error_string) will:
 *  call the function
 *  display an error if the function failed
 *  return the function return value
 */
#define ALSA_CALL(call, error) \
    ({ \
        int err_code = call; \
        if (err_code < 0) \
            JAMI_ERROR(error ": {}", snd_strerror(err_code)); \
        err_code; \
    })

void
AlsaLayer::stopCaptureStream()
{
    if (captureHandle_ && ALSA_CALL(snd_pcm_drop(captureHandle_), "Unable to stop capture") >= 0) {
        is_capture_running_ = false;
        is_capture_prepared_ = false;
    }
}

void
AlsaLayer::closeCaptureStream()
{
    if (is_capture_prepared_ and is_capture_running_)
        stopCaptureStream();

    JAMI_LOG("Alsa: Closing capture stream");
    if (is_capture_open_ && ALSA_CALL(snd_pcm_close(captureHandle_), "Unable to close capture") >= 0) {
        is_capture_open_ = false;
        captureHandle_ = nullptr;
    }
}

void
AlsaLayer::startCaptureStream()
{
    if (captureHandle_ and not is_capture_running_)
        if (ALSA_CALL(snd_pcm_start(captureHandle_), "Unable to start capture") >= 0)
            is_capture_running_ = true;
}

void
AlsaLayer::stopPlaybackStream()
{
    if (playbackHandle_ and is_playback_running_) {
        if (ALSA_CALL(snd_pcm_drop(playbackHandle_), "Unable to stop playback") >= 0) {
            is_playback_running_ = false;
        }
    }
}

void
AlsaLayer::closePlaybackStream()
{
    if (is_playback_running_)
        stopPlaybackStream();

    if (is_playback_open_) {
        JAMI_LOG("Alsa: Closing playback stream");
        if (ALSA_CALL(snd_pcm_close(playbackHandle_), "Coulnd't close playback") >= 0)
            is_playback_open_ = false;
        playbackHandle_ = nullptr;
    }
}

void
AlsaLayer::closeRingtoneStream()
{
    if (ringtoneHandle_) {
        ALSA_CALL(snd_pcm_drop(ringtoneHandle_), "Unable to stop ringtone");
        ALSA_CALL(snd_pcm_close(ringtoneHandle_), "Unable to close ringtone");
        ringtoneHandle_ = nullptr;
    }
}

void
AlsaLayer::startPlaybackStream()
{
    is_playback_running_ = true;
}

void
AlsaLayer::prepareCaptureStream()
{
    if (is_capture_open_ and not is_capture_prepared_)
        if (ALSA_CALL(snd_pcm_prepare(captureHandle_), "Unable to prepare capture") >= 0)
            is_capture_prepared_ = true;
}

bool
AlsaLayer::alsa_set_params(snd_pcm_t* pcm_handle, AudioFormat& format)
{
#define TRY(call, error) \
    do { \
        if (ALSA_CALL(call, error) < 0) \
            return false; \
    } while (0)

    snd_pcm_hw_params_t* hwparams;
    snd_pcm_hw_params_alloca(&hwparams);

    constexpr unsigned ALSA_PERIOD_SIZE = 160;
    constexpr unsigned ALSA_NB_PERIOD = 8;
    constexpr unsigned ALSA_BUFFER_SIZE = ALSA_PERIOD_SIZE * ALSA_NB_PERIOD;

    snd_pcm_uframes_t period_size = ALSA_PERIOD_SIZE;
    snd_pcm_uframes_t buffer_size = ALSA_BUFFER_SIZE;
    unsigned int periods = ALSA_NB_PERIOD;

    snd_pcm_uframes_t period_size_min = 0;
    snd_pcm_uframes_t period_size_max = 0;
    snd_pcm_uframes_t buffer_size_min = 0;
    snd_pcm_uframes_t buffer_size_max = 0;

#define HW pcm_handle, hwparams /* hardware parameters */
    TRY(snd_pcm_hw_params_any(HW), "hwparams init");

    TRY(snd_pcm_hw_params_set_access(HW, SND_PCM_ACCESS_RW_INTERLEAVED), "access type");

    if (snd_pcm_hw_params_test_format(HW, SND_PCM_FORMAT_FLOAT_LE) == 0) {
        TRY(snd_pcm_hw_params_set_format(HW, SND_PCM_FORMAT_FLOAT_LE), "sample format");
        format = format.withSampleFormat(AV_SAMPLE_FMT_FLT);
    } else {
        TRY(snd_pcm_hw_params_set_format(HW, SND_PCM_FORMAT_S16_LE), "sample format");
        format = format.withSampleFormat(AV_SAMPLE_FMT_S16);
    }

    TRY(snd_pcm_hw_params_set_rate_resample(HW, 0), "hardware sample rate"); /* prevent software resampling */
    TRY(snd_pcm_hw_params_set_rate_near(HW, &format.sample_rate, nullptr), "sample rate");

    // Query the max number of channels supported by the hardware
    unsigned int max_channels = 0;
    if (snd_pcm_hw_params_get_channels_max(hwparams, &max_channels) < 0) {
        JAMI_WARNING("Unable to query hardware channel number, defaulting to 2");
        max_channels = 2;
    }

    format.nb_channels = std::min(max_channels, 2u);
    if (format.nb_channels == 0)
        format.nb_channels = 2;

    TRY(snd_pcm_hw_params_set_channels_near(HW, &format.nb_channels), "channel count");

    snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
    snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
    snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, nullptr);
    snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, nullptr);
    JAMI_DEBUG("Buffer size range from {} to {}", buffer_size_min, buffer_size_max);
    JAMI_DEBUG("Period size range from {} to {}", period_size_min, period_size_max);
    buffer_size = buffer_size > buffer_size_max ? buffer_size_max : buffer_size;
    buffer_size = buffer_size < buffer_size_min ? buffer_size_min : buffer_size;
    period_size = period_size > period_size_max ? period_size_max : period_size;
    period_size = period_size < period_size_min ? period_size_min : period_size;

    TRY(snd_pcm_hw_params_set_buffer_size_near(HW, &buffer_size), "Unable to set buffer size for playback");
    TRY(snd_pcm_hw_params_set_period_size_near(HW, &period_size, nullptr), "Unable to set period size for playback");
    TRY(snd_pcm_hw_params_set_periods_near(HW, &periods, nullptr), "Unable to set number of periods for playback");
    TRY(snd_pcm_hw_params(HW), "hwparams");

    snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
    snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr);
    snd_pcm_hw_params_get_rate(hwparams, &format.sample_rate, nullptr);
    snd_pcm_hw_params_get_channels(hwparams, &format.nb_channels);
    JAMI_DEBUG("ALSA period_size = {}", period_size);
    JAMI_DEBUG("ALSA buffer_size = {}", buffer_size);

    if (2 * period_size > buffer_size) {
        JAMI_ERROR("buffer to small, unable to use");
        return false;
    }

#undef HW

    JAMI_DEBUG("{} using format {}",
               (snd_pcm_stream(pcm_handle) == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
               format.toString());

    snd_pcm_sw_params_t* swparams = nullptr;
    snd_pcm_sw_params_alloca(&swparams);

#define SW pcm_handle, swparams /* software parameters */
    snd_pcm_sw_params_current(SW);
    TRY(snd_pcm_sw_params_set_start_threshold(SW, period_size * 2), "start threshold");
    TRY(snd_pcm_sw_params(SW), "sw parameters");
#undef SW

    return true;

#undef TRY
}

// TODO first frame causes broken pipe (underrun) because not enough data is sent
// we should wait until the handle is ready
void
AlsaLayer::write(const AudioFrame& buffer, snd_pcm_t* handle)
{
    int err = snd_pcm_writei(handle, (const void*) buffer.pointer()->data[0], buffer.pointer()->nb_samples);

    if (err < 0)
        snd_pcm_recover(handle, err, 0);

    if (err >= 0)
        return;

    switch (err) {
    case -EPIPE:
    case -ESTRPIPE:
    case -EIO: {
        snd_pcm_status_t* status;
        snd_pcm_status_alloca(&status);

        if (ALSA_CALL(snd_pcm_status(handle, status), "Unable to get playback handle status") >= 0)
            if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
                stopPlaybackStream();
                startPlaybackStream();
            }

        ALSA_CALL(snd_pcm_writei(handle, (const void*) buffer.pointer()->data[0], buffer.pointer()->nb_samples),
                  "XRUN handling failed");
        break;
    }

    case -EBADFD: {
        snd_pcm_status_t* status;
        snd_pcm_status_alloca(&status);

        if (ALSA_CALL(snd_pcm_status(handle, status), "Unable to get playback handle status") >= 0) {
            if (snd_pcm_status_get_state(status) == SND_PCM_STATE_SETUP) {
                JAMI_ERROR(
                    "Writing in state SND_PCM_STATE_SETUP, should be SND_PCM_STATE_PREPARED or SND_PCM_STATE_RUNNING");
                int error = snd_pcm_prepare(handle);

                if (error < 0) {
                    JAMI_ERROR("Failed to prepare handle: {}", snd_strerror(error));
                    stopPlaybackStream();
                }
            }
        }

        break;
    }

    default:
        JAMI_ERROR("Unknown write error, dropping frames: {}", snd_strerror(err));
        stopPlaybackStream();
        break;
    }
}

std::unique_ptr<AudioFrame>
AlsaLayer::read(unsigned frames)
{
    if (snd_pcm_state(captureHandle_) == SND_PCM_STATE_XRUN) {
        prepareCaptureStream();
        startCaptureStream();
    }

    auto ret = std::make_unique<AudioFrame>(audioInputFormat_, frames);
    int err = snd_pcm_readi(captureHandle_, ret->pointer()->data[0], frames);

    if (err >= 0) {
        ret->pointer()->nb_samples = err;
        return ret;
    }

    switch (err) {
    case -EPIPE:
    case -ESTRPIPE:
    case -EIO: {
        snd_pcm_status_t* status;
        snd_pcm_status_alloca(&status);

        if (ALSA_CALL(snd_pcm_status(captureHandle_, status), "Get status failed") >= 0)
            if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
                stopCaptureStream();
                prepareCaptureStream();
                startCaptureStream();
            }

        JAMI_ERROR("XRUN capture ignored ({})", snd_strerror(err));
        break;
    }

    case -EPERM:
        JAMI_ERROR("Unable to capture, EPERM ({})", snd_strerror(err));
        prepareCaptureStream();
        startCaptureStream();
        break;
    }

    return {};
}

std::string
AlsaLayer::buildDeviceTopo(const std::string& plugin, int card)
{
    if (plugin == PCM_DEFAULT)
        return fmt::format("plughw:{},0", card);

    return fmt::format("{}:{}", plugin, card);
}

static bool
safeUpdate(snd_pcm_t* handle, long& samples)
{
    samples = snd_pcm_avail_update(handle);

    if (samples < 0) {
        samples = snd_pcm_recover(handle, samples, 0);

        if (samples < 0) {
            JAMI_ERROR("Got unrecoverable error from snd_pcm_avail_update: {}", snd_strerror(samples));
            return false;
        }
    }

    return true;
}

static std::vector<std::string>
getValues(const std::vector<HwIDPair>& deviceMap)
{
    std::vector<std::string> audioDeviceList;
    audioDeviceList.reserve(deviceMap.size());

    for (const auto& dev : deviceMap)
        audioDeviceList.push_back(dev.second);

    return audioDeviceList;
}

std::vector<std::string>
AlsaLayer::getCaptureDeviceList() const
{
    return getValues(getAudioDeviceIndexMap(true));
}

std::vector<std::string>
AlsaLayer::getPlaybackDeviceList() const
{
    return getValues(getAudioDeviceIndexMap(false));
}

std::vector<HwIDPair>
AlsaLayer::getAudioDeviceIndexMap(bool getCapture) const
{
    snd_ctl_t* handle;
    snd_ctl_card_info_t* info;
    snd_pcm_info_t* pcminfo;
    snd_ctl_card_info_alloca(&info);
    snd_pcm_info_alloca(&pcminfo);

    int numCard = -1;

    std::vector<HwIDPair> audioDevice;

    if (snd_card_next(&numCard) < 0 || numCard < 0)
        return audioDevice;

    do {
        std::string name = fmt::format("hw:{}", numCard);

        if (snd_ctl_open(&handle, name.c_str(), 0) == 0) {
            if (snd_ctl_card_info(handle, info) == 0) {
                snd_pcm_info_set_device(pcminfo, 0);
                snd_pcm_info_set_stream(pcminfo, getCapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);

                int err;
                if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
                    JAMI_WARNING("Unable to get info for {} device {}: {}",
                                 getCapture ? "capture" : "playback",
                                 name,
                                 snd_strerror(err));
                } else {
                    auto description = fmt::format("{} - {}",
                                                   snd_ctl_card_info_get_name(info),
                                                   snd_pcm_info_get_name(pcminfo));
                    JAMI_DEBUG("{} device {} : {} [{}]",
                               getCapture ? "capture" : "playback",
                               snd_ctl_card_info_get_id(info),
                               name,
                               description);

                    // The number of the sound card is associated with a string description
                    audioDevice.emplace_back(numCard, std::move(description));
                }
            }

            snd_ctl_close(handle);
        }
    } while (snd_card_next(&numCard) >= 0 && numCard >= 0);

    return audioDevice;
}

bool
AlsaLayer::soundCardIndexExists(int card, AudioDeviceType stream)
{
    std::string name = fmt::format("hw:{}", card);

    snd_ctl_t* handle;
    if (snd_ctl_open(&handle, name.c_str(), 0) != 0)
        return false;

    snd_pcm_info_t* pcminfo;
    snd_pcm_info_alloca(&pcminfo);
    snd_pcm_info_set_stream(pcminfo,
                            stream == AudioDeviceType::PLAYBACK ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE);
    bool ret = snd_ctl_pcm_info(handle, pcminfo) >= 0;
    snd_ctl_close(handle);
    return ret;
}

int
AlsaLayer::getAudioDeviceIndex(const std::string& description, AudioDeviceType type) const
{
    std::vector<HwIDPair> devices = getAudioDeviceIndexMap(type == AudioDeviceType::CAPTURE);

    for (const auto& dev : devices)
        if (dev.second == description)
            return dev.first;

    // else return the default one
    return 0;
}

std::string
AlsaLayer::getAudioDeviceName(int index, AudioDeviceType type) const
{
    // a bit ugly and wrong.. i do not know how to implement it better in alsalayer.
    // in addition, for now it is used in pulselayer only due to alsa and pulse layers api
    // differences. but after some tweaking in alsalayer, it could be used in it too.
    switch (type) {
    case AudioDeviceType::PLAYBACK:
    case AudioDeviceType::RINGTONE:
        return getPlaybackDeviceList().at(index);

    case AudioDeviceType::CAPTURE:
        return getCaptureDeviceList().at(index);
    default:
        // Should never happen
        JAMI_ERROR("Unexpected type");
        return "";
    }
}

void
AlsaLayer::capture()
{
    if (!captureHandle_ or !is_capture_running_)
        return;

    snd_pcm_wait(captureHandle_, 10);

    int toGetFrames = snd_pcm_avail_update(captureHandle_);
    if (toGetFrames < 0)
        JAMI_ERROR("ALSA: Mic error: {}", snd_strerror(toGetFrames));
    if (toGetFrames <= 0)
        return;

    const int framesPerBufferAlsa = 2048;
    toGetFrames = std::min(framesPerBufferAlsa, toGetFrames);
    if (auto r = read(toGetFrames)) {
        putRecorded(std::move(r));
    } else
        JAMI_ERROR("ALSA MIC : Unable to read!");
}

void
AlsaLayer::playback()
{
    if (!playbackHandle_)
        return;

    snd_pcm_wait(playbackHandle_, 20);

    long maxFrames = 0;
    if (not safeUpdate(playbackHandle_, maxFrames))
        return;

    if (auto toPlay = getToPlay(audioFormat_, maxFrames)) {
        write(*toPlay, playbackHandle_);
    }
}

void
AlsaLayer::ringtone()
{
    if (!ringtoneHandle_)
        return;

    long ringtoneAvailFrames = 0;
    if (not safeUpdate(ringtoneHandle_, ringtoneAvailFrames))
        return;

    if (auto toRing = getToRing(audioFormat_, ringtoneAvailFrames)) {
        write(*toRing, ringtoneHandle_);
    }
}

// Translate an ALSA card number back to its 0-based position in the device list.
static int
cardToListIndex(const std::vector<HwIDPair>& cards, int cardNum)
{
    for (int i = 0; i < static_cast<int>(cards.size()); ++i)
        if (cards[i].first == cardNum)
            return i;
    return 0;
}

int
AlsaLayer::getIndexCapture() const
{
    return cardToListIndex(getAudioDeviceIndexMap(true), indexIn_);
}

int
AlsaLayer::getIndexPlayback() const
{
    return cardToListIndex(getAudioDeviceIndexMap(false), indexOut_);
}

int
AlsaLayer::getIndexRingtone() const
{
    return cardToListIndex(getAudioDeviceIndexMap(false), indexRing_);
}

void
AlsaLayer::updatePreference(AudioPreference& preference, int index, AudioDeviceType type)
{
    // 'index' is a list position in getCaptureDeviceList() / getPlaybackDeviceList(),
    // but the preference stores the actual ALSA card number. Translate here.
    auto toCardNum = [&](bool capture) -> int {
        auto cards = getAudioDeviceIndexMap(capture);
        if (index >= 0 && index < static_cast<int>(cards.size()))
            return cards[index].first;
        return 0;
    };

    switch (type) {
    case AudioDeviceType::PLAYBACK:
        preference.setAlsaCardout(toCardNum(false));
        break;

    case AudioDeviceType::CAPTURE:
        preference.setAlsaCardin(toCardNum(true));
        break;

    case AudioDeviceType::RINGTONE:
        preference.setAlsaCardRingtone(toCardNum(false));
        break;

    default:
        break;
    }
}

} // namespace jami
