[mythtv] [PATCH] volume control for OSS and ALSA (Part 2)

David George david at thegeorges.us
Tue Oct 26 14:46:05 UTC 2004


Here is part two (threaded to make it easier to find).

--
David

-------------- next part --------------
#ifndef VOLUMECONTROLBASE_H_
#define VOLUMECONTROLBASE_H_

#include <qstring.h>

#include "volumecontrol.h"

using namespace std;

class VolumeControlBase : public VolumeControl
{
  public:
    VolumeControlBase(bool setstartingvolume = true);
    virtual ~VolumeControlBase();

    virtual void AdjustCurrentVolume(int change);
    virtual void SetMute(bool on);
    virtual void ToggleMute(void);
    virtual bool GetMute(void) { return mute; }
    virtual kMuteState IterateMutedChannels(void);

  protected:
    // You need to implement the following functions
    virtual void OpenMixer(bool setstartingvolume) = 0;
    virtual void CloseMixer(void) = 0;
    virtual void SetCurrentVolume(QString control, int value, bool save) = 0;

    QString mixer_control;  // e.g. "PCM"

    int internal_volume;

    bool mute;

    kMuteState current_mute_state;

    float volume_range_multiplier;
    long playback_vol_min, playback_vol_max;
};

#endif
-------------- next part --------------
#include <cstdio>
#include <cstdlib>

using namespace std;

#include "mythcontext.h"
#include "volumecontrolbase.h"

VolumeControlBase::VolumeControlBase(bool setstartingvolume)
{
    mute = false;
    current_mute_state = MUTE_OFF;

    // call OpenMixer from the concrete class
}

VolumeControlBase::~VolumeControlBase()
{
    // call CloseMixer from the concrete class
}

void VolumeControlBase::OpenMixer(bool setstartingvolume)
{
    // Override me
}

void VolumeControlBase::CloseMixer()
{
    // Override me
}

void VolumeControlBase::AdjustCurrentVolume(int change)
{
    if (change < 0 && (change * -1) < volume_range_multiplier)
        change = (int)(volume_range_multiplier * -1 - 0.5);
    else if (change > 0 && change < volume_range_multiplier)
        change = (int)(volume_range_multiplier + 0.5);

    VERBOSE(VB_AUDIO, QString("AdjustCurrentVolume %1 (mute=%2, internal=%3)")
            .arg(change).arg(mute).arg(internal_volume));

    int newvol = GetCurrentVolume() + change;

    SetCurrentVolume(mixer_control, newvol, true);
}

void VolumeControlBase::SetMute(bool on)
{
    VERBOSE(VB_AUDIO, QString("SetMute %1").arg(on));

    int actual_volume;

    if (on) {
        actual_volume = 0;
    } else {
        actual_volume = internal_volume;
    }

    mute = on;

    SetCurrentVolume(mixer_control, actual_volume, false);
}

void VolumeControlBase::ToggleMute(void)
{
    SetMute(!mute);
}

kMuteState VolumeControlBase::IterateMutedChannels(void)
{
// current_mute_state is initialized to "MUTE_OFF".  If individual muting
// is enabled, each call to SetMute will advance to the next state:
// MUTE_OFF -> MUTE_LEFT -> MUTE_RIGHT -> MUTE_BOTH -> MUTE_OFF
    switch (current_mute_state)
    {
       case MUTE_OFF:
           current_mute_state = MUTE_LEFT;
           break;
       case MUTE_LEFT:
           current_mute_state = MUTE_RIGHT;
           break;
       case MUTE_RIGHT:
           current_mute_state = MUTE_BOTH;
           break;
       case MUTE_BOTH:
           current_mute_state = MUTE_OFF;
           break;
    }

    SetCurrentVolume(mixer_control, internal_volume, false);

    return (current_mute_state);
}
-------------- next part --------------
#ifndef VOLUMECONTROLOSS_H_
#define VOLUMECONTROLOSS_H_

#include <qstring.h>

#include "volumecontrolbase.h"

using namespace std;

class VolumeControlOSS : public VolumeControlBase
{
  public:
    VolumeControlOSS(bool setstartingvolume = true);
    virtual ~VolumeControlOSS();

    virtual int GetCurrentVolume(void);
    virtual void SetCurrentVolume(QString control, int value, bool save);

  protected:
    virtual void OpenMixer(bool setstartingvolume);
    virtual void CloseMixer(void);
    virtual void SetupMixer(void);

  private:
    int mixerfd;
};

#endif
-------------- next part --------------
#include "volumecontroloss.h"

#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <cstdio>
#include <unistd.h>

#include <iostream>
using namespace std;

#include "mythcontext.h"

VolumeControlOSS::VolumeControlOSS(bool setstartingvolume)
            : VolumeControlBase(setstartingvolume)
{
    mixerfd = -1;
    volume_range_multiplier = 0.0;
    playback_vol_min = 0;
    playback_vol_max = 100;
    
    OpenMixer(setstartingvolume);
}

VolumeControlOSS::~VolumeControlOSS()
{
    if (mixerfd > -1)
        close(mixerfd);
}

void VolumeControlOSS::OpenMixer(bool setstartingvolume)
{
    int volume;
    
    mixer_control = gContext->GetSetting("MixerControl", "PCM");

    SetupMixer();

    if (mixerfd > -1 && setstartingvolume)
    {
        volume = gContext->GetNumSetting("MasterMixerVolume", 80);
        SetCurrentVolume("Master", volume, false);

        volume = gContext->GetNumSetting("PCMMixerVolume", 80);
        SetCurrentVolume("PCM", volume, false);
    }

    internal_volume = GetCurrentVolume();
}

void VolumeControlOSS::CloseMixer(void)
{
    close(mixerfd);
    mixerfd = -1;
}
    
void VolumeControlOSS::SetupMixer(void)
{
    QString device = gContext->GetSetting("MixerDevice", "/dev/mixer");

    VERBOSE(VB_AUDIO, QString("Opening mixer %1").arg(device));

    mixerfd = open(device.ascii(), O_RDONLY);

    if (mixerfd < 0)
        VERBOSE(VB_IMPORTANT,
                QString("VolumeControlOSS: mixer open (%1) err %2")
                .arg(device).arg(errno));
}

int VolumeControlOSS::GetCurrentVolume(void)
{
    if (mixerfd < 0 || mute)
        return internal_volume;

    int realvol, osscontrol;

    if (mixer_control == "Master")
        osscontrol = SOUND_MIXER_VOLUME;
    else
        osscontrol = SOUND_MIXER_PCM;

    int ret = ioctl(mixerfd, MIXER_READ(osscontrol), &realvol);
    if (ret < 0)
    {
        perror("Reading PCM volume: ");
    }
    internal_volume = realvol & 0xff; // just use the left channel

    VERBOSE(VB_AUDIO, QString("current %1 volume is %2")
            .arg(mixer_control).arg(internal_volume));

    return internal_volume;
}

void VolumeControlOSS::SetCurrentVolume(QString control, int value, bool save)
{
    int osscontrol, volume = value;

    if (volume < 0)
        volume = 0;
    if (volume > 100)
        volume = 100;

    VERBOSE(VB_AUDIO, QString("Setting %1 volume to %2")
            .arg(control).arg(volume));

    if (mixerfd > -1 && !mute)
    {
        int realvol = (volume << 8) + volume;

        if (control == "Master")
            osscontrol = SOUND_MIXER_VOLUME;
        else
            osscontrol = SOUND_MIXER_PCM;

        int ret = ioctl(mixerfd, MIXER_WRITE(osscontrol), &realvol);
        if (ret < 0)
            perror("Setting volume: ");
    }

    if (!mute)
        internal_volume = volume;

    VERBOSE(VB_AUDIO, QString("new volume is %1 (mute=%2, internal=%3)")
            .arg(volume).arg(mute).arg(internal_volume));

    if (save)
    {
        QString controlLabel = mixer_control + "MixerVolume";
        gContext->SaveSetting(controlLabel, volume);
    }
}
-------------- next part --------------
#ifndef VOLUMECONTROLALSA_H_
#define VOLUMECONTROLALSA_H_

#include <qstring.h>

#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
#include <alsa/asoundlib.h>

#include "volumecontrolbase.h"

using namespace std;

class VolumeControlALSA : public VolumeControlBase
{
  public:
    VolumeControlALSA(bool setstartingvolume = true);
    virtual ~VolumeControlALSA();

    virtual int GetCurrentVolume(void);
    virtual void SetCurrentVolume(QString control, int value, bool save);

  protected:
    virtual void OpenMixer(bool setstartingvolume);
    virtual void CloseMixer(void);
    virtual void SetupMixer(void);

  private:
    inline void GetVolumeRange(void);

    snd_mixer_t          *mixer_handle;
    snd_mixer_elem_t     *elem;
    snd_mixer_selem_id_t *sid;
};

#endif
-------------- next part --------------
#include <cstdio>
#include <cstdlib>

using namespace std;

#include "mythcontext.h"
#include "volumecontrolalsa.h"

VolumeControlALSA::VolumeControlALSA(bool setstartingvolume)
            : VolumeControlBase(setstartingvolume)
{
    mixer_handle = NULL;

    OpenMixer(setstartingvolume);
}

VolumeControlALSA::~VolumeControlALSA()
{
    if (mixer_handle != NULL)
        snd_mixer_close(mixer_handle);
}

void VolumeControlALSA::OpenMixer(bool setstartingvolume)
{
    int volume;
    
    mixer_control = gContext->GetSetting("MixerControl", "PCM");

    SetupMixer();

    if (mixer_handle != NULL && setstartingvolume)
    {
        volume = gContext->GetNumSetting("MasterMixerVolume", 80);
        SetCurrentVolume("Master", volume, false);

        volume = gContext->GetNumSetting("PCMMixerVolume", 80);
        SetCurrentVolume("PCM", volume, false);
    }

    internal_volume = GetCurrentVolume();
}

void VolumeControlALSA::CloseMixer(void)
{
    snd_mixer_close(mixer_handle);
    mixer_handle = NULL;
}

void VolumeControlALSA::SetupMixer(void)
{
    int err;

    QString device = gContext->GetSetting("MixerDevice", "default");

    if (mixer_handle != NULL)
        snd_mixer_close(mixer_handle);

    VERBOSE(VB_AUDIO, QString("Opening mixer %1").arg(device));
    
    if ((err = snd_mixer_open(&mixer_handle, 0)) < 0)
    {
        Error(QString("mixer open err %1: %2")
              .arg(err).arg(snd_strerror(err)));
        mixer_handle = NULL;
        return;
    }

    if ((err = snd_mixer_attach(mixer_handle, device.ascii())) < 0)
    {
        Error(QString("mixer attach err %1: %2")
              .arg(err).arg(snd_strerror(err)));
        snd_mixer_close(mixer_handle);
        mixer_handle = NULL;
        return;
    }

    if ((err = snd_mixer_selem_register(mixer_handle, NULL, NULL)) < 0)
    {
        Error(QString("mixer register err %1: %2")
              .arg(err).arg(snd_strerror(err)));
        snd_mixer_close(mixer_handle);
        mixer_handle = NULL;
        return;
    }

    if ((err = snd_mixer_load(mixer_handle)) < 0)
    {
        Error(QString("mixer load err %1: %2")
              .arg(err).arg(snd_strerror(err)));
        snd_mixer_close(mixer_handle);
        mixer_handle = NULL;
        return;
    }
}

int VolumeControlALSA::GetCurrentVolume(void)
{
    if (mixer_handle == NULL || mute)
        return internal_volume;

    long actual_volume, volume;

    snd_mixer_selem_id_alloca(&sid);
    snd_mixer_selem_id_set_index(sid, 0);
    snd_mixer_selem_id_set_name(sid, mixer_control.ascii());

    if ((elem = snd_mixer_find_selem(mixer_handle, sid)) == NULL)
    {
        Error(QString("mixer unable to find control %1").arg(mixer_control));
        snd_mixer_close(mixer_handle);
        mixer_handle = NULL;
        return 0;
    }

    GetVolumeRange();

    snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT,
                                        &actual_volume);
    volume = (int)((actual_volume - playback_vol_min) *
                   volume_range_multiplier);

    internal_volume = volume;

    VERBOSE(VB_AUDIO, QString("current %1 volume is %2 (internal=%3)")
            .arg(mixer_control).arg(volume).arg(internal_volume));

    return internal_volume;
}

void VolumeControlALSA::SetCurrentVolume(QString control, int value, bool save)
{
    int err, set_vol;
    int volume = value;

    if (volume < 0)
        volume = 0;
    if (volume > 100)
        volume = 100;

    VERBOSE(VB_AUDIO, QString("Setting %1 volume to %2")
            .arg(control).arg(volume));
    
    if (mixer_handle != NULL && !mute)
    {
        snd_mixer_selem_id_alloca(&sid);
        snd_mixer_selem_id_set_index(sid, 0);
        snd_mixer_selem_id_set_name(sid, control.ascii());

        if ((elem = snd_mixer_find_selem(mixer_handle, sid)) == NULL)
        {
            Error(QString("mixer unable to find control %1").arg(control));
            return;
        }

        GetVolumeRange();

        if (current_mute_state == MUTE_LEFT ||
              current_mute_state == MUTE_BOTH)
            set_vol = 0;
        else
            set_vol = (int)(volume / volume_range_multiplier +
                            playback_vol_min + 0.5);

        if ((err = snd_mixer_selem_set_playback_volume(elem,
            SND_MIXER_SCHN_FRONT_LEFT, set_vol)) < 0)
        {
            Error(QString("mixer set left channel err %1: %2")
                  .arg(err).arg(snd_strerror(err)));
            return;
        }
        else
        {
            VERBOSE(VB_AUDIO, QString("left vol set to %1").arg(set_vol));
        }

        if (current_mute_state == MUTE_RIGHT ||
              current_mute_state == MUTE_BOTH)
            set_vol = 0;
        else
            set_vol = (int)(volume / volume_range_multiplier +
                            playback_vol_min + 0.5);

        if ((err = snd_mixer_selem_set_playback_volume(elem,
            SND_MIXER_SCHN_FRONT_RIGHT, set_vol)) < 0)
        {
            Error(QString("mixer set right channel err %1: %2")
                  .arg(err).arg(snd_strerror(err)));
            return;
        }
        else
        {
            VERBOSE(VB_AUDIO, QString("right vol set to %1").arg(set_vol));
        }
    }

    if (!mute)
        internal_volume = volume;

    VERBOSE(VB_AUDIO, QString("new volume is %1 (mute=%2, internal=%3)")
            .arg(volume).arg(mute).arg(internal_volume));

    if (save)
    {
      QString controlLabel = mixer_control + "MixerVolume";
      gContext->SaveSetting(controlLabel, volume);
    }
}

inline void VolumeControlALSA::GetVolumeRange(void)
{
    snd_mixer_selem_get_playback_volume_range(elem, &playback_vol_min,
                                              &playback_vol_max);
    volume_range_multiplier = (100.0 / (float)(playback_vol_max -
                                               playback_vol_min));
    
    VERBOSE(VB_AUDIO, QString("Volume range is %1 to %2, mult=%3")
            .arg(playback_vol_min).arg(playback_vol_max)
            .arg(volume_range_multiplier));
}


More information about the mythtv-dev mailing list