/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include <cassert>

#include "audio_mixer_manager_alsa_linux.h"
#include "trace.h"

extern webrtc_adm_linux_alsa::AlsaSymbolTable AlsaSymbolTable;

// Accesses ALSA functions through our late-binding symbol table instead of
// directly. This way we don't have to link to libalsa, which means our binary
// will work on systems that don't have it.
#define LATE(sym) \
  LATESYM_GET(webrtc_adm_linux_alsa::AlsaSymbolTable, &AlsaSymbolTable, sym)

namespace webrtc
{

AudioMixerManagerLinuxALSA::AudioMixerManagerLinuxALSA(const WebRtc_Word32 id) :
    _critSect(*CriticalSectionWrapper::CreateCriticalSection()),
    _id(id),
    _outputMixerHandle(NULL),
    _inputMixerHandle(NULL),
    _outputMixerElement(NULL),
    _inputMixerElement(NULL)
{
    WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id,
                 "%s constructed", __FUNCTION__);

    memset(_outputMixerStr, 0, kAdmMaxDeviceNameSize);
    memset(_inputMixerStr, 0, kAdmMaxDeviceNameSize);
}

AudioMixerManagerLinuxALSA::~AudioMixerManagerLinuxALSA()
{
    WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id,
                 "%s destructed", __FUNCTION__);

    Close();

    delete &_critSect;
}

// ============================================================================
//                                    PUBLIC METHODS
// ============================================================================

WebRtc_Word32 AudioMixerManagerLinuxALSA::Close()
{
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%s",
                 __FUNCTION__);

    CriticalSectionScoped lock(&_critSect);

    CloseSpeaker();
    CloseMicrophone();

    return 0;

}

WebRtc_Word32 AudioMixerManagerLinuxALSA::CloseSpeaker()
{
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%s",
                 __FUNCTION__);

    CriticalSectionScoped lock(&_critSect);

    int errVal = 0;

    if (_outputMixerHandle != NULL)
    {
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Closing playout mixer");
        LATE(snd_mixer_free)(_outputMixerHandle);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error freeing playout mixer: %s",
                         LATE(snd_strerror)(errVal));
        }
        errVal = LATE(snd_mixer_detach)(_outputMixerHandle, _outputMixerStr);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error detachinging playout mixer: %s",
                         LATE(snd_strerror)(errVal));
        }
        errVal = LATE(snd_mixer_close)(_outputMixerHandle);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error snd_mixer_close(handleMixer) errVal=%d",
                         errVal);
        }
        _outputMixerHandle = NULL;
        _outputMixerElement = NULL;
    }
    memset(_outputMixerStr, 0, kAdmMaxDeviceNameSize);

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::CloseMicrophone()
{
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%s", __FUNCTION__);

    CriticalSectionScoped lock(&_critSect);

    int errVal = 0;

    if (_inputMixerHandle != NULL)
    {
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Closing record mixer");

        LATE(snd_mixer_free)(_inputMixerHandle);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error freeing record mixer: %s",
                         LATE(snd_strerror)(errVal));
        }
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Closing record mixer 2");

        errVal = LATE(snd_mixer_detach)(_inputMixerHandle, _inputMixerStr);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error detachinging record mixer: %s",
                         LATE(snd_strerror)(errVal));
        }
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Closing record mixer 3");

        errVal = LATE(snd_mixer_close)(_inputMixerHandle);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error snd_mixer_close(handleMixer) errVal=%d",
                         errVal);
        }

        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Closing record mixer 4");
        _inputMixerHandle = NULL;
        _inputMixerElement = NULL;
    }
    memset(_inputMixerStr, 0, kAdmMaxDeviceNameSize);

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::OpenSpeaker(char* deviceName)
{
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "AudioMixerManagerLinuxALSA::OpenSpeaker(name=%s)", deviceName);

    CriticalSectionScoped lock(&_critSect);

    int errVal = 0;

    // Close any existing output mixer handle
    //
    if (_outputMixerHandle != NULL)
    {
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Closing playout mixer");

        LATE(snd_mixer_free)(_outputMixerHandle);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error freeing playout mixer: %s",
                         LATE(snd_strerror)(errVal));
        }
        errVal = LATE(snd_mixer_detach)(_outputMixerHandle, _outputMixerStr);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error detachinging playout mixer: %s",
                         LATE(snd_strerror)(errVal));
        }
        errVal = LATE(snd_mixer_close)(_outputMixerHandle);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error snd_mixer_close(handleMixer) errVal=%d",
                         errVal);
        }
    }
    _outputMixerHandle = NULL;
    _outputMixerElement = NULL;

    errVal = LATE(snd_mixer_open)(&_outputMixerHandle, 0);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "snd_mixer_open(&_outputMixerHandle, 0) - error");
        return -1;
    }

    char controlName[kAdmMaxDeviceNameSize] = { 0 };
    GetControlName(controlName, deviceName);

    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "     snd_mixer_attach(_outputMixerHandle, %s)", controlName);

    errVal = LATE(snd_mixer_attach)(_outputMixerHandle, controlName);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     snd_mixer_attach(_outputMixerHandle, %s) error: %s",
                     controlName, LATE(snd_strerror)(errVal));
        _outputMixerHandle = NULL;
        return -1;
    }
    strcpy(_outputMixerStr, controlName);

    errVal = LATE(snd_mixer_selem_register)(_outputMixerHandle, NULL, NULL);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     snd_mixer_selem_register(_outputMixerHandle,"
                     " NULL, NULL), error: %s",
                     LATE(snd_strerror)(errVal));
        _outputMixerHandle = NULL;
        return -1;
    }

    // Load and find the proper mixer element
    if (LoadSpeakerMixerElement() < 0)
    {
        return -1;
    }

    if (_outputMixerHandle != NULL)
    {
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "  the output mixer device is now open (0x%x)",
                     _outputMixerHandle);
    }

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::OpenMicrophone(char *deviceName)
{
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "AudioMixerManagerLinuxALSA::OpenMicrophone(name=%s)",
                 deviceName);

    CriticalSectionScoped lock(&_critSect);

    int errVal = 0;

    // Close any existing input mixer handle
    //
    if (_inputMixerHandle != NULL)
    {
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Closing record mixer");

        LATE(snd_mixer_free)(_inputMixerHandle);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error freeing record mixer: %s",
                         LATE(snd_strerror)(errVal));
        }
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Closing record mixer");

        errVal = LATE(snd_mixer_detach)(_inputMixerHandle, _inputMixerStr);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error detachinging record mixer: %s",
                         LATE(snd_strerror)(errVal));
        }
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Closing record mixer");

        errVal = LATE(snd_mixer_close)(_inputMixerHandle);
        if (errVal < 0)
        {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "     Error snd_mixer_close(handleMixer) errVal=%d",
                         errVal);
        }
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Closing record mixer");
    }
    _inputMixerHandle = NULL;
    _inputMixerElement = NULL;

    errVal = LATE(snd_mixer_open)(&_inputMixerHandle, 0);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     snd_mixer_open(&_inputMixerHandle, 0) - error");
        return -1;
    }

    char controlName[kAdmMaxDeviceNameSize] = { 0 };
    GetControlName(controlName, deviceName);

    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "     snd_mixer_attach(_inputMixerHandle, %s)", controlName);

    errVal = LATE(snd_mixer_attach)(_inputMixerHandle, controlName);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     snd_mixer_attach(_inputMixerHandle, %s) error: %s",
                     controlName, LATE(snd_strerror)(errVal));

        _inputMixerHandle = NULL;
        return -1;
    }
    strcpy(_inputMixerStr, controlName);

    errVal = LATE(snd_mixer_selem_register)(_inputMixerHandle, NULL, NULL);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     snd_mixer_selem_register(_inputMixerHandle,"
                     " NULL, NULL), error: %s",
                     LATE(snd_strerror)(errVal));

        _inputMixerHandle = NULL;
        return -1;
    }
    // Load and find the proper mixer element
    if (LoadMicMixerElement() < 0)
    {
        return -1;
    }

    if (_inputMixerHandle != NULL)
    {
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "  the input mixer device is now open (0x%x)",
                     _inputMixerHandle);
    }

    return 0;
}

bool AudioMixerManagerLinuxALSA::SpeakerIsInitialized() const
{
    WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, "%s", __FUNCTION__);

    return (_outputMixerHandle != NULL);
}

bool AudioMixerManagerLinuxALSA::MicrophoneIsInitialized() const
{
    WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, "%s",
                 __FUNCTION__);

    return (_inputMixerHandle != NULL);
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::SetSpeakerVolume(
    WebRtc_UWord32 volume)
{
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "AudioMixerManagerLinuxALSA::SetSpeakerVolume(volume=%u)",
                 volume);

    CriticalSectionScoped lock(&_critSect);

    if (_outputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable output mixer element exists");
        return -1;
    }

    int errVal =
        LATE(snd_mixer_selem_set_playback_volume_all)(_outputMixerElement,
                                                      volume);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     Error changing master volume: %s",
                     LATE(snd_strerror)(errVal));
        return -1;
    }

    return (0);
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::SpeakerVolume(
    WebRtc_UWord32& volume) const
{

    if (_outputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable output mixer element exists");
        return -1;
    }

    long int vol(0);

    int
        errVal = LATE(snd_mixer_selem_get_playback_volume)(
            _outputMixerElement,
            (snd_mixer_selem_channel_id_t) 0,
            &vol);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "Error getting outputvolume: %s",
                     LATE(snd_strerror)(errVal));
        return -1;
    }
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "     AudioMixerManagerLinuxALSA::SpeakerVolume() => vol=%i",
                 vol);

    volume = static_cast<WebRtc_UWord32> (vol);

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::MaxSpeakerVolume(
    WebRtc_UWord32& maxVolume) const
{

    if (_outputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avilable output mixer element exists");
        return -1;
    }

    long int minVol(0);
    long int maxVol(0);

    int errVal =
        LATE(snd_mixer_selem_get_playback_volume_range)(_outputMixerElement,
                                                        &minVol, &maxVol);

    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "     Playout hardware volume range, min: %d, max: %d",
                 minVol, maxVol);

    if (maxVol <= minVol)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     Error getting get_playback_volume_range: %s",
                     LATE(snd_strerror)(errVal));
    }

    maxVolume = static_cast<WebRtc_UWord32> (maxVol);

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::MinSpeakerVolume(
    WebRtc_UWord32& minVolume) const
{

    if (_outputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable output mixer element exists");
        return -1;
    }

    long int minVol(0);
    long int maxVol(0);

    int errVal =
        LATE(snd_mixer_selem_get_playback_volume_range)(_outputMixerElement,
                                                        &minVol, &maxVol);

    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "     Playout hardware volume range, min: %d, max: %d",
                 minVol, maxVol);

    if (maxVol <= minVol)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     Error getting get_playback_volume_range: %s",
                     LATE(snd_strerror)(errVal));
    }

    minVolume = static_cast<WebRtc_UWord32> (minVol);

    return 0;
}

// TL: Have done testnig with these but they don't seem reliable and
// they were therefore not added
/*
 // ----------------------------------------------------------------------------
 //    SetMaxSpeakerVolume
 // ----------------------------------------------------------------------------

 WebRtc_Word32 AudioMixerManagerLinuxALSA::SetMaxSpeakerVolume(
     WebRtc_UWord32 maxVolume)
 {

 if (_outputMixerElement == NULL)
 {
 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
 "  no avaliable output mixer element exists");
 return -1;
 }

 long int minVol(0);
 long int maxVol(0);

 int errVal = snd_mixer_selem_get_playback_volume_range(
 _outputMixerElement, &minVol, &maxVol);
 if ((maxVol <= minVol) || (errVal != 0))
 {
 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
  "     Error getting playback volume range: %s", snd_strerror(errVal));
 }

 maxVol = maxVolume;
 errVal = snd_mixer_selem_set_playback_volume_range(
 _outputMixerElement, minVol, maxVol);
 WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
  "     Playout hardware volume range, min: %d, max: %d", minVol, maxVol);
 if (errVal != 0)
 {
 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
  "     Error setting playback volume range: %s", snd_strerror(errVal));
 return -1;
 }

 return 0;
 }

 // ----------------------------------------------------------------------------
 //    SetMinSpeakerVolume
 // ----------------------------------------------------------------------------

 WebRtc_Word32 AudioMixerManagerLinuxALSA::SetMinSpeakerVolume(
     WebRtc_UWord32 minVolume)
 {

 if (_outputMixerElement == NULL)
 {
 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
 "  no avaliable output mixer element exists");
 return -1;
 }

 long int minVol(0);
 long int maxVol(0);

 int errVal = snd_mixer_selem_get_playback_volume_range(
 _outputMixerElement, &minVol, &maxVol);
 if ((maxVol <= minVol) || (errVal != 0))
 {
 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
  "     Error getting playback volume range: %s", snd_strerror(errVal));
 }

 minVol = minVolume;
 errVal = snd_mixer_selem_set_playback_volume_range(
 _outputMixerElement, minVol, maxVol);
 WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
 "     Playout hardware volume range, min: %d, max: %d", minVol, maxVol);
 if (errVal != 0)
 {
 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
 "     Error setting playback volume range: %s", snd_strerror(errVal));
 return -1;
 }

 return 0;
 }
 */

WebRtc_Word32 AudioMixerManagerLinuxALSA::SpeakerVolumeStepSize(
    WebRtc_UWord16& stepSize) const
{

    if (_outputMixerHandle == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable output mixer exists");
        return -1;
    }

    // The step size is always 1 for ALSA
    stepSize = 1;

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::SpeakerVolumeIsAvailable(
    bool& available)
{
    if (_outputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable output mixer element exists");
        return -1;
    }

    available = LATE(snd_mixer_selem_has_playback_volume)(_outputMixerElement);

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::SpeakerMuteIsAvailable(
    bool& available)
{
    if (_outputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable output mixer element exists");
        return -1;
    }

    available = LATE(snd_mixer_selem_has_playback_switch)(_outputMixerElement);

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::SetSpeakerMute(bool enable)
{
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "AudioMixerManagerLinuxALSA::SetSpeakerMute(enable=%u)",
                 enable);

    CriticalSectionScoped lock(&_critSect);

    if (_outputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable output mixer element exists");
        return -1;
    }

    // Ensure that the selected speaker destination has a valid mute control.
    bool available(false);
    SpeakerMuteIsAvailable(available);
    if (!available)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  it is not possible to mute the speaker");
        return -1;
    }

    // Note value = 0 (off) means muted
    int errVal =
        LATE(snd_mixer_selem_set_playback_switch_all)(_outputMixerElement,
                                                      !enable);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     Error setting playback switch: %s",
                     LATE(snd_strerror)(errVal));
        return -1;
    }

    return (0);
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::SpeakerMute(bool& enabled) const
{

    if (_outputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable output mixer exists");
        return -1;
    }

    // Ensure that the selected speaker destination has a valid mute control.
    bool available =
        LATE(snd_mixer_selem_has_playback_switch)(_outputMixerElement);
    if (!available)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  it is not possible to mute the speaker");
        return -1;
    }

    int value(false);

    // Retrieve one boolean control value for a specified mute-control
    //
    int
        errVal = LATE(snd_mixer_selem_get_playback_switch)(
            _outputMixerElement,
            (snd_mixer_selem_channel_id_t) 0,
            &value);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     Error getting playback switch: %s",
                     LATE(snd_strerror)(errVal));
        return -1;
    }

    // Note value = 0 (off) means muted
    enabled = (bool) !value;

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::MicrophoneMuteIsAvailable(
    bool& available)
{
    if (_inputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer element exists");
        return -1;
    }

    available = LATE(snd_mixer_selem_has_capture_switch)(_inputMixerElement);
    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::SetMicrophoneMute(bool enable)
{
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "AudioMixerManagerLinuxALSA::SetMicrophoneMute(enable=%u)",
                 enable);

    CriticalSectionScoped lock(&_critSect);

    if (_inputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer element exists");
        return -1;
    }

    // Ensure that the selected microphone destination has a valid mute control.
    bool available(false);
    MicrophoneMuteIsAvailable(available);
    if (!available)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  it is not possible to mute the microphone");
        return -1;
    }

    // Note value = 0 (off) means muted
    int errVal =
        LATE(snd_mixer_selem_set_capture_switch_all)(_inputMixerElement,
                                                     !enable);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     Error setting capture switch: %s",
                     LATE(snd_strerror)(errVal));
        return -1;
    }

    return (0);
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::MicrophoneMute(bool& enabled) const
{

    if (_inputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer exists");
        return -1;
    }

    // Ensure that the selected microphone destination has a valid mute control.
    bool available =
        LATE(snd_mixer_selem_has_capture_switch)(_inputMixerElement);
    if (!available)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  it is not possible to mute the microphone");
        return -1;
    }

    int value(false);

    // Retrieve one boolean control value for a specified mute-control
    //
    int
        errVal = LATE(snd_mixer_selem_get_capture_switch)(
            _inputMixerElement,
            (snd_mixer_selem_channel_id_t) 0,
            &value);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     Error getting capture switch: %s",
                     LATE(snd_strerror)(errVal));
        return -1;
    }

    // Note value = 0 (off) means muted
    enabled = (bool) !value;

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::MicrophoneBoostIsAvailable(
    bool& available)
{
    if (_inputMixerHandle == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer exists");
        return -1;
    }

    // Microphone boost cannot be enabled through ALSA Simple Mixer Interface
    available = false;

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::SetMicrophoneBoost(bool enable)
{
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "AudioMixerManagerLinuxALSA::SetMicrophoneBoost(enable=%u)",
                 enable);

    CriticalSectionScoped lock(&_critSect);

    if (_inputMixerHandle == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer exists");
        return -1;
    }

    // Ensure that the selected microphone destination has a valid mute control.
    bool available(false);
    MicrophoneMuteIsAvailable(available);
    if (!available)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  it is not possible to enable microphone boost");
        return -1;
    }

    // It is assumed that the call above fails!

    return (0);
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::MicrophoneBoost(bool& enabled) const
{

    if (_inputMixerHandle == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer exists");
        return -1;
    }

    // Microphone boost cannot be enabled on this platform!
    enabled = false;

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::MicrophoneVolumeIsAvailable(
    bool& available)
{
    if (_inputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer element exists");
        return -1;
    }

    available = LATE(snd_mixer_selem_has_capture_volume)(_inputMixerElement);

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::SetMicrophoneVolume(
    WebRtc_UWord32 volume)
{
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "AudioMixerManagerLinuxALSA::SetMicrophoneVolume(volume=%u)",
                 volume);

    CriticalSectionScoped lock(&_critSect);

    if (_inputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer element exists");
        return -1;
    }

    int
        errVal =
            LATE(snd_mixer_selem_set_capture_volume_all)(_inputMixerElement,
                                                         volume);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     Error changing microphone volume: %s",
                     LATE(snd_strerror)(errVal));
        return -1;
    }

    return (0);
}

// TL: Have done testnig with these but they don't seem reliable and
// they were therefore not added
/*
 // ----------------------------------------------------------------------------
 //    SetMaxMicrophoneVolume
 // ----------------------------------------------------------------------------

 WebRtc_Word32 AudioMixerManagerLinuxALSA::SetMaxMicrophoneVolume(
     WebRtc_UWord32 maxVolume)
 {

 if (_inputMixerElement == NULL)
 {
 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
  "  no avaliable output mixer element exists");
 return -1;
 }

 long int minVol(0);
 long int maxVol(0);

 int errVal = snd_mixer_selem_get_capture_volume_range(_inputMixerElement,
  &minVol, &maxVol);
 if ((maxVol <= minVol) || (errVal != 0))
 {
 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
  "     Error getting capture volume range: %s", snd_strerror(errVal));
 }

 maxVol = (long int)maxVolume;
 printf("min %d max %d", minVol, maxVol);
 errVal = snd_mixer_selem_set_capture_volume_range(_inputMixerElement, minVol, maxVol);
 WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
 "     Capture hardware volume range, min: %d, max: %d", minVol, maxVol);
 if (errVal != 0)
 {
 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
  "     Error setting capture volume range: %s", snd_strerror(errVal));
 return -1;
 }

 return 0;
 }

 // ----------------------------------------------------------------------------
 //    SetMinMicrophoneVolume
 // ----------------------------------------------------------------------------

 WebRtc_Word32 AudioMixerManagerLinuxALSA::SetMinMicrophoneVolume(
 WebRtc_UWord32 minVolume)
 {

 if (_inputMixerElement == NULL)
 {
 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
  "  no avaliable output mixer element exists");
 return -1;
 }

 long int minVol(0);
 long int maxVol(0);

 int errVal = snd_mixer_selem_get_capture_volume_range(
 _inputMixerElement, &minVol, &maxVol);
 if (maxVol <= minVol)
 {
 //maxVol = 255;
 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
  "     Error getting capture volume range: %s", snd_strerror(errVal));
 }

 printf("min %d max %d", minVol, maxVol);
 minVol = (long int)minVolume;
 errVal = snd_mixer_selem_set_capture_volume_range(
 _inputMixerElement, minVol, maxVol);
 WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
  "     Capture hardware volume range, min: %d, max: %d", minVol, maxVol);
 if (errVal != 0)
 {
 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
  "     Error setting capture volume range: %s", snd_strerror(errVal));
 return -1;
 }

 return 0;
 }
 */

WebRtc_Word32 AudioMixerManagerLinuxALSA::MicrophoneVolume(
    WebRtc_UWord32& volume) const
{

    if (_inputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer element exists");
        return -1;
    }

    long int vol(0);

    int
        errVal =
            LATE(snd_mixer_selem_get_capture_volume)(
                _inputMixerElement,
                (snd_mixer_selem_channel_id_t) 0,
                &vol);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "Error getting inputvolume: %s",
                     LATE(snd_strerror)(errVal));
        return -1;
    }
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "     AudioMixerManagerLinuxALSA::MicrophoneVolume() => vol=%i",
                 vol);

    volume = static_cast<WebRtc_UWord32> (vol);

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::MaxMicrophoneVolume(
    WebRtc_UWord32& maxVolume) const
{

    if (_inputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer element exists");
        return -1;
    }

    long int minVol(0);
    long int maxVol(0);

    // check if we have mic volume at all
    if (!LATE(snd_mixer_selem_has_capture_volume)(_inputMixerElement))
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     No microphone volume available");
        return -1;
    }

    int errVal =
        LATE(snd_mixer_selem_get_capture_volume_range)(_inputMixerElement,
                                                       &minVol, &maxVol);

    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "     Microphone hardware volume range, min: %d, max: %d",
                 minVol, maxVol);
    if (maxVol <= minVol)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     Error getting microphone volume range: %s",
                     LATE(snd_strerror)(errVal));
    }

    maxVolume = static_cast<WebRtc_UWord32> (maxVol);

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::MinMicrophoneVolume(
    WebRtc_UWord32& minVolume) const
{

    if (_inputMixerElement == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer element exists");
        return -1;
    }

    long int minVol(0);
    long int maxVol(0);

    int errVal =
        LATE(snd_mixer_selem_get_capture_volume_range)(_inputMixerElement,
                                                       &minVol, &maxVol);

    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "     Microphone hardware volume range, min: %d, max: %d",
                 minVol, maxVol);
    if (maxVol <= minVol)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     Error getting microphone volume range: %s",
                     LATE(snd_strerror)(errVal));
    }

    minVolume = static_cast<WebRtc_UWord32> (minVol);

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::MicrophoneVolumeStepSize(
    WebRtc_UWord16& stepSize) const
{

    if (_inputMixerHandle == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  no avaliable input mixer exists");
        return -1;
    }

    // The step size is always 1 for ALSA
    stepSize = 1;

    return 0;
}

// ============================================================================
//                                 Private Methods
// ============================================================================

WebRtc_Word32 AudioMixerManagerLinuxALSA::LoadMicMixerElement() const
{
    int errVal = LATE(snd_mixer_load)(_inputMixerHandle);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "snd_mixer_load(_inputMixerHandle), error: %s",
                     LATE(snd_strerror)(errVal));
        _inputMixerHandle = NULL;
        return -1;
    }

    snd_mixer_elem_t *elem = NULL;
    snd_mixer_elem_t *micElem = NULL;
    unsigned mixerIdx = 0;
    const char *selemName = NULL;

    // Find and store handles to the right mixer elements
    for (elem = LATE(snd_mixer_first_elem)(_inputMixerHandle); elem; elem
        = LATE(snd_mixer_elem_next)(elem), mixerIdx++)
    {
        if (LATE(snd_mixer_selem_is_active)(elem))
        {
            selemName = LATE(snd_mixer_selem_get_name)(elem);
            if (strcmp(selemName, "Capture") == 0) // "Capture", "Mic"
            {
                _inputMixerElement = elem;
                WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice,
                             _id, "     Capture element set");
            } else if (strcmp(selemName, "Mic") == 0)
            {
                micElem = elem;
                WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice,
                             _id, "     Mic element found");
            }
        }

        if (_inputMixerElement)
        {
            // Use the first Capture element that is found
            // The second one may not work
            break;
        }
    }

    if (_inputMixerElement == NULL)
    {
        // We didn't find a Capture handle, use Mic.
        if (micElem != NULL)
        {
            _inputMixerElement = micElem;
            WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                         "     Using Mic as capture volume.");
        } else
        {
            _inputMixerElement = NULL;
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "Could not find capture volume on the mixer.");

            return -1;
        }
    }

    return 0;
}

WebRtc_Word32 AudioMixerManagerLinuxALSA::LoadSpeakerMixerElement() const
{
    int errVal = LATE(snd_mixer_load)(_outputMixerHandle);
    if (errVal < 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "     snd_mixer_load(_outputMixerHandle), error: %s",
                     LATE(snd_strerror)(errVal));
        _outputMixerHandle = NULL;
        return -1;
    }

    snd_mixer_elem_t *elem = NULL;
    snd_mixer_elem_t *masterElem = NULL;
    snd_mixer_elem_t *speakerElem = NULL;
    unsigned mixerIdx = 0;
    const char *selemName = NULL;

    // Find and store handles to the right mixer elements
    for (elem = LATE(snd_mixer_first_elem)(_outputMixerHandle); elem; elem
        = LATE(snd_mixer_elem_next)(elem), mixerIdx++)
    {
        if (LATE(snd_mixer_selem_is_active)(elem))
        {
            selemName = LATE(snd_mixer_selem_get_name)(elem);
            WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                         "snd_mixer_selem_get_name %d: %s =%x", mixerIdx,
                         selemName, elem);

            // "Master", "PCM", "Wave", "Master Mono", "PC Speaker", "PCM", "Wave"
            if (strcmp(selemName, "PCM") == 0)
            {
                _outputMixerElement = elem;
                WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice,
                             _id, "     PCM element set");
            } else if (strcmp(selemName, "Master") == 0)
            {
                masterElem = elem;
                WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice,
                             _id, "     Master element found");
            } else if (strcmp(selemName, "Speaker") == 0)
            {
                speakerElem = elem;
                WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice,
                             _id, "     Speaker element found");
            }
        }

        if (_outputMixerElement)
        {
            // We have found the element we want
            break;
        }
    }

    // If we didn't find a PCM Handle, use Master or Speaker
    if (_outputMixerElement == NULL)
    {
        if (masterElem != NULL)
        {
            _outputMixerElement = masterElem;
            WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                         "     Using Master as output volume.");
        } else if (speakerElem != NULL)
        {
            _outputMixerElement = speakerElem;
            WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                         "     Using Speaker as output volume.");
        } else
        {
            _outputMixerElement = NULL;
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "Could not find output volume in the mixer.");
            return -1;
        }
    }

    return 0;
}

void AudioMixerManagerLinuxALSA::GetControlName(char* controlName,
                                                char* deviceName) const
{
    // Example
    // deviceName: "front:CARD=Intel,DEV=0"
    // controlName: "hw:CARD=Intel"
    char* pos1 = strchr(deviceName, ':');
    char* pos2 = strchr(deviceName, ',');
    if (!pos2)
    {
        // Can also be default:CARD=Intel
        pos2 = &deviceName[strlen(deviceName)];
    }
    if (pos1 && pos2)
    {
        strcpy(controlName, "hw");
        int nChar = (int) (pos2 - pos1);
        strncpy(&controlName[2], pos1, nChar);
        controlName[2 + nChar] = '\0';
    } else
    {
        strcpy(controlName, deviceName);
    }

}

}
