Logo Search packages:      
Sourcecode: pbbuttonsd version File versions  Download package

module_ossmixer.c

/* --------------------------------------------------------------------------
 * module_mixer.c
 * code for the OSS mixer module, controling volume levels
 *
 * Copyright 2002 Matthias Grimm
 *
 * 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
 * 2 of the License, or (at your option) any later version.
 * -------------------------------------------------------------------------*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#ifdef WITH_OSS

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/soundcard.h>
#include "pbbinput.h"

#include <pbb.h>

#include "gettext_macros.h"
#include "input_manager.h"
#include "config_manager.h"
#include "module_mixer.h"
#include "module_ossmixer.h"
#include "support.h"

struct moddata_ossmixer {
      char *mixerdev;   /* name of the mixer device */
      unsigned short keyvolup;
      unsigned short modvolup;
      unsigned short keyvoldn;
      unsigned short modvoldn;
      unsigned short keymute;
      unsigned short modmute;
      struct modflags_ossmixer flags;
      int mixerdevmask;       /* supported channels from mixer */
      int userdevmask;        /* desired channels from user */
      int volume;       /* last volume set */
      int master;       /* master sound channel */
} modbase_ossmixer;

#define COUNT_MIXERCHANNELS 25
char *mixerchannels[] = {"volume", "bass", "treble", "synth", "pcm", "speaker", \
        "line", "mic", "cd", "imix", "altpcm", "reclev", "igain", "ogain", \
            "line1", "line2", "line3", "digital1", "digital2", "digital3", "phonein", \
            "phoneout", "video", "radio", "monitor"};

int
ossmixer_init ()
{
      struct moddata_ossmixer *base = &modbase_ossmixer;
      static char devbuffer_mixer[STDBUFFERLEN];
      int sid;

      base->mixerdev          = devbuffer_mixer;
      base->keyvolup          = KEY_VOLUMEUP;
      base->modvolup          = MOD_NONE;
      base->keyvoldn          = KEY_VOLUMEDOWN;
      base->modvoldn          = MOD_NONE;
      base->keymute           = KEY_MUTE;
      base->modmute           = MOD_NONE;
      base->master            = SOUND_MIXER_VOLUME;
      base->mixerdevmask      = 1 << SOUND_MIXER_VOLUME;
      base->userdevmask = 0;
      base->volume            = -1;
      base->flags.mute  = 0;
      base->flags.mixer_delayed = 0;
      base->flags.init_complete = 0;
      base->flags.open    = 0;

      devbuffer_mixer[0] = 0;
      
      if ((sid = registerCfgSection ("MODULE MIXER")) == 0) {
            print_msg (PBB_ERR, _("Can't register configuration section %s, out of memory."), "MODULE MIXER");
            return E_NOMEM;
      } else {
            /* options always to process */
            registerCfgOptionString (sid, "OSS_Device", TAG_MIXERDEVICE, 0,
                        NULL);
            registerCfgOptionString (sid, "OSS_Channels", TAG_MIXERCHANNELS, 0,
                        NULL);
            registerCfgOptionString (sid, "dev_Mixer", TAG_MIXERDEVICE, FLG_RONLY,
                        NULL);
            registerCfgOptionString (sid, "MixerChannels", TAG_MIXERCHANNELS, FLG_RONLY,
                        NULL);
      
            /* options only to process if this module is active (open) */
            registerCfgOptionInt (sid, "Volume", TAG_VOLUME, 0,
                        N_("initial volume level"));
            registerCfgOptionBool (sid, "Speakers_muted", TAG_MUTE, 0,
                        N_("mute after startup?"));
            registerCfgOptionKey (sid, "VolumeUpKey", TAG_VOLUMEUPKEY, TAG_VOLUMEUPMOD, 0,
                        NULL);
            registerCfgOptionKey (sid, "VolumeDownKey", TAG_VOLUMEDOWNKEY, TAG_VOLUMEDOWNMOD, 0,
                        NULL);
            registerCfgOptionKey (sid, "MuteKey", TAG_MUTEKEY, TAG_MUTEMOD, 0,
                        NULL);
            registerCfgOptionBool (sid, "MixerInitDelay", TAG_MIXERINITDELAY, 0,
                        NULL);
      }
      register_function (QUERYQUEUE, ossmixer_query);
      register_function (CONFIGQUEUE, ossmixer_configure);
      return 0;
}

int
ossmixer_open (struct tagitem *taglist)
{
      struct moddata_ossmixer *base = &modbase_ossmixer;
      char *devname;
      int rc;

      devname = (char *) tagfind (taglist, TAG_MIXERDEVICE, (long) DEFAULT_MIXER);
      if ((rc = copy_path (devname, base->mixerdev, TYPE_CHARDEV, CPFLG_MAYBEMISSED)))
            return rc;

      ossmixer_setuserdevmask ((char *) tagfind (taglist, TAG_MIXERCHANNELS, (long) "volume"));

      if ((base->flags.mixer_delayed = tagfind (taglist, TAG_MIXERINITDELAY, 0)) == 0)
            if (ossmixer_finish_init () == -1)
                  return E_NOSUPPORT;

      if (!base->flags.init_complete) {
            /* can't set volume with only partly initialized mixer */
            tagskip (taglist, TAG_VOLUME);
            tagskip (taglist, TAG_MUTE);
      }
      
      base->flags.open = 1;
      register_function (KBDQUEUE, ossmixer_keyboard);
      return 0;
}

int
ossmixer_finish_init ()
{
      struct moddata_ossmixer *base = &modbase_ossmixer;
      int fd, devmask;

      if ((fd = open(base->mixerdev, O_RDWR)) >= 0) {     /* open mixer device */
            if ((base->volume = get_volume(fd, base->master)) != -1) {
                  if ((devmask = get_mixer_devmask(fd)) != -1)
                        base->mixerdevmask = devmask;
                  else {
                        base->mixerdevmask = 1 << SOUND_MIXER_VOLUME;
                        print_msg (PBB_WARN, _("Can't get devmask from mixer [%s]; using default.\n"), base->mixerdev);
                  }
                  base->flags.init_complete = 1;        /* mixer setup completed */
                  close (fd);
                  return 0;
            }
            print_msg (PBB_ERR, _("Can't get volume of master channel [%s].\n"), mixerchannels[base->master]);
            close(fd);
      } else if (base->flags.mixer_delayed == 0)
            print_msg (PBB_ERR, _("Can't open mixer device [%s]. %s\n"), base->mixerdev, strerror(errno));
      return -1;
}

int
ossmixer_close ()
{
      struct moddata_ossmixer *base = &modbase_ossmixer;

      base->flags.open = 0;
      return 0;
}

int
ossmixer_exit ()
{
      return 0;
}

/* ---- */

void
ossmixer_keyboard (struct tagitem *taglist)
{
      struct moddata_ossmixer *base = &modbase_ossmixer;
      int code, value, mod, step;

      code = (int) tagfind (taglist, TAG_KEYCODE, 0);
      value = (int) tagfind (taglist, TAG_KEYREPEAT, 0);
      mod = (int) tagfind (taglist, TAG_MODIFIER, 0);

      if (value) {
            if ((code == base->keyvolup) && ((mod & ~MOD_SHIFT) == base->modvolup)) {
                  step = (mod & MOD_SHIFT) ? +1 : +10;
            } else if ((code == base->keyvoldn) && ((mod & ~MOD_SHIFT) == base->modvoldn)) {
                  step = (mod & MOD_SHIFT) ? -1 : -10;
            } else if ((code == base->keymute) && (mod == base->modmute) && (value == 1)) {
                  step = 0;
            } else return;

            ossmixer_set_and_send (step);
      }
}

void
ossmixer_query (struct tagitem *taglist)
{
      ossmixer_handle_tags (MODE_QUERY, taglist);
}

void
ossmixer_configure (struct tagitem *taglist)
{
      ossmixer_handle_tags (MODE_CONFIG, taglist);
}

void
ossmixer_handle_tags (int cfgure, struct tagitem *taglist)
{
      struct moddata_ossmixer *base = &modbase_ossmixer;
      int err;

      while (taglist->tag != TAG_END) {

            /* tags that would always be processed even if this module was closed
             */
            switch (taglist->tag) {
            case TAG_MIXERDEVICE:
                  if (cfgure) {
                        if ((err = copy_path ((char *) taglist->data, base->mixerdev, TYPE_CHARDEV, CPFLG_MAYBEMISSED)))
                              tagerror (taglist, err);
                  } else
                        taglist->data = (long) base->mixerdev;
                  break;
            case TAG_MIXERCHANNELS:
                  if (cfgure) ossmixer_setuserdevmask ((char *) taglist->data);
                  else        taglist->data = (long) ossmixer_getuserdevmask(base->userdevmask);
/*                else        taglist->data = (long) ossmixer_getuserdevmask(base->userdevmask & base->mixerdevmask);*/
                  break;
            }

            /* tags that will be processed only if the module is open.
             */
            if (base->flags.open) {
                  switch (taglist->tag) {
                  case TAG_VOLUME:
                        if (cfgure) {
                              if ((taglist->data - base->volume) != 0)
                                    ossmixer_set_and_send (taglist->data - base->volume);
                        } else
                              taglist->data = base->volume;
                        break;
                  case TAG_MUTE:
                        if (cfgure) {
                              if (base->flags.mute != taglist->data)
                                    ossmixer_set_and_send (0);
                        } else
                              taglist->data = base->flags.mute;
                        break;
                  }
            }
            
            /* tags that could be configured even if the module is closed but only be
             * read if the module is open.
             */
            if (cfgure || base->flags.open) {
                  switch (taglist->tag) {
                  case TAG_VOLUMEUPKEY:
                        if (cfgure) base->keyvolup = taglist->data;
                        else        taglist->data = base->keyvolup;
                        break;
                  case TAG_VOLUMEUPMOD:
                        if (cfgure) base->modvolup = taglist->data;
                        else        taglist->data = base->modvolup;
                        break;
                  case TAG_VOLUMEDOWNKEY:
                        if (cfgure) base->keyvoldn = taglist->data;
                        else        taglist->data = base->keyvoldn;
                        break;
                  case TAG_VOLUMEDOWNMOD:
                        if (cfgure) base->modvoldn = taglist->data;
                        else        taglist->data = base->modvoldn;
                        break;
                  case TAG_MUTEKEY:
                        if (cfgure) base->keymute = taglist->data;
                        else        taglist->data = base->keymute;
                        break;
                  case TAG_MUTEMOD:
                        if (cfgure) base->modmute = taglist->data;
                        else        taglist->data = base->modmute;
                        break;
                  case TAG_MIXERINITDELAY:
                        if (cfgure) base->flags.mixer_delayed  = taglist->data;
                        else        taglist->data = base->flags.mixer_delayed;
                        break;
                  }
            }
            taglist++;
      }
}

void
ossmixer_setuserdevmask (char *channels)
{
      struct moddata_ossmixer *base = &modbase_ossmixer;
        char *token, buffer[200];
      int n;

      base->userdevmask = 0;
      strncpy (buffer, channels, 200);
      cleanup_buffer (buffer);
      if ((token = strtok(buffer, ",\n")))
            do {
                  for (n=COUNT_MIXERCHANNELS -1; n >= 0; n--)
                        if (!strcasecmp (mixerchannels[n], token)) {
                              if (base->userdevmask == 0)
                                    base->master = n;   /* first channel in list becomes master */
                              base->userdevmask |= 1 << n;
                              break;
                        }
            } while ((token = strtok(0,",\n")) != NULL);
}

char*
ossmixer_getuserdevmask (int devmask)
{
      static char channels[200];
      int n;

      channels[0] = 0;
      for (n=0; n < COUNT_MIXERCHANNELS; n++) {
            if ((devmask >> n & 1) == 1) {
                  if (channels[0] != 0)
                        strcat(channels, ", ");
                  strcat (channels, mixerchannels[n]);
            }
      }
      return channels;
}

int
ossmixer_set_and_send (int increment)
{
      struct moddata_ossmixer *base = &modbase_ossmixer;
      int volume;

      if ((volume = increment_master_volume (increment)) != -1) {
            set_slaves_volume ();
            singletag_to_clients (CHANGEVALUE, base->flags.mute ? TAG_MUTE : TAG_VOLUME, volume);
      }
      return volume;
}

/* This function increases, decreases or mute/unmute sound channels. It opens
   the mixer devices as long as it is needed. The volume level is tracked internal
   and only syncronized time by time with the hardware volume level to take
   external mixer adjustments into account. If everything was alright the new
   volume level would be returned. If the mixer device could not be opened
   the function will return -1 without changing the internal volume level.
   If the mixer isn't already set up, this function will do it. */

int
increment_master_volume(int increment)
{
      struct moddata_ossmixer *base = &modbase_ossmixer;
      int volume = base->volume;
      int fd;

      if (base->flags.init_complete == 0)       /* is mixer setup already completed? */
            if ((ossmixer_finish_init ()) != 0)    /* no, then do it now */
                  return volume;                     /* Oops, try it again later */

      if ((fd = open(base->mixerdev, O_RDWR)) >= 0) {     /* open mixer device */
            if ((volume = get_volume(fd, base->master)) != -1) {
                  if ((volume != 0) && (base->flags.mute))   /* someone else increased volume? */
                        base->flags.mute = 0;                       /* so we can't stay muted. */
                  if (base->flags.mute == 0)
                      if ((base->volume < volume - VOLUME_FR)
                        || (base->volume > volume + VOLUME_FR))  /* someone else changed volume */
                              base->volume = volume;               /* so we must sychonize our volume level */
            }
            volume = base->volume + increment;
            if (volume > VOLUME_MAX)
                  volume = VOLUME_MAX;
            if (volume < VOLUME_MIN)
                  volume = VOLUME_MIN;
            if (increment != 0) {                       /* volume should be increased/decreased? */
                  base->volume = volume;       /* store new volume level */
                  base->flags.mute = 0;            /* unmute channels */
            } else {
                  if (base->flags.mute == 0) {    /* channels not already muted? */
                        volume = 0;
                        base->flags.mute = 1;        /* channels are muted. */
                  } else {
                        volume = base->volume;    /* set former volume level again */
                        base->flags.mute = 0;        /* unmute channels */
                  }
            }
            set_volume(fd, base->master, volume);
            close(fd);
      } else
            volume = -1;
      return volume;
}

/* This function sets the volume level of every slave to the same
    level as the master. If the mixer device couldn't be opened,
    nothing would be done. */

void
set_slaves_volume ()
{
      struct moddata_ossmixer *base = &modbase_ossmixer;
      int volume = (base->flags.mute ? 0 : base->volume);
      int n, fd, devmask;

      devmask = base->userdevmask & base->mixerdevmask;
      if ((fd = open(base->mixerdev, O_RDWR)) >= 0) {     /* open mixer device */
            for (n=0; n < COUNT_MIXERCHANNELS; n++)
                  if ((devmask >> n & 1) == 1)
                        set_volume (fd, n, volume);
            close(fd);
      }
}

/* This function sets a channel's volume level */

int
set_volume(int fd, int channel, int volume)
{
      volume += 256*volume;
      if (ioctl (fd, MIXER_WRITE(channel), &volume) >= 0)
            return (volume & 255);
      return -1;
}

/* This function asks the mixer for a channel's volume level
   It returns the average volume of the left and right channel */

int
get_volume(int fd, int channel)
{
      int volume;
      if (ioctl (fd, MIXER_READ(channel), &volume) >= 0)
            return ((volume & 255) + ((volume >> 8) & 255)) >> 1;
      return -1;
}

/* This function asks the mixer which sound channels it supports */

int
get_mixer_devmask(int fd)
{
      int devmask;
      if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask) >= 0)
            return devmask;
      return -1;
}

#endif /* WITH_OSS */

Generated by  Doxygen 1.6.0   Back to index