aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--NEWS1
-rw-r--r--doc/mpd.conf.524
-rw-r--r--doc/mpdconf.example16
-rw-r--r--src/Makefile.am3
-rw-r--r--src/audio.c55
-rw-r--r--src/audio.h5
-rw-r--r--src/command.c6
-rw-r--r--src/main.c5
-rw-r--r--src/mixer.h33
-rw-r--r--src/mixer/alsa_mixer.c206
-rw-r--r--src/mixer/oss_mixer.c197
-rw-r--r--src/output/alsa_plugin.c22
-rw-r--r--src/output/oss_plugin.c19
-rw-r--r--src/output_api.h12
-rw-r--r--src/volume.c508
-rw-r--r--src/volume.h11
16 files changed, 675 insertions, 448 deletions
diff --git a/NEWS b/NEWS
index 9ceeae55e..632668ed8 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,5 @@
ver 0.15 - (200?/??/??)
+* Rewritten mixer code to support multiple mixers
* Add audio archive extraction support:
- bzip2
- iso9660
diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5
index 5feefc36e..935bd9b2f 100644
--- a/doc/mpd.conf.5
+++ b/doc/mpd.conf.5
@@ -157,18 +157,23 @@ Linear interpolator, very fast, poor quality.
For an up-to-date list of available converters, please see the libsamplerate
documentation (available online at <\fBhttp://www.mega-nerd.com/SRC/\fP>).
.TP
-.B mixer_type <oss, alsa or software>
-This specifies which mixer to use. The default depends on what audio output
-support mpd was built with.
+.B mixer_type <alsa, oss, software or hardware>
+This specifies which mixer to use. The default is hardware and depends on
+what audio output support mpd was built with. Options alsa and oss are
+legacy and should not be used in new configs, but when set mixer_device
+and mixer_control will apply.
.TP
.B mixer_device <mixer dev>
This specifies which mixer to use. The default for oss is "/dev/mixer"; the
-default for alsa is "default".
+default for alsa is "default". This option is deprecated and should not be
+used. Look at the mix_device option of corresponding output device instead.
.TP
.B mixer_control <mixer ctrl>
This specifies which mixer control to use (sometimes referred to as the
"device"). Examples of mixer controls are PCM, Line1, Master, etc. An example
-for OSS is "Pcm", and an example for alsa is "PCM".
+for OSS is "Pcm", and an example for alsa is "PCM". This option is deprecated
+and should not be used. Look at the mix_control option of corresponding
+output device instead.
.TP
.B replaygain <album or track>
If specified, mpd will adjust the volume of songs played using ReplayGain tags
@@ -276,6 +281,15 @@ whatever audio format is passed to the audio output.
.B device <dev>
This specifies the device to use for audio output. The default is "default".
.TP
+.B mix_device <mixer dev>
+This specifies which mixer to use. The default for oss is "/dev/mixer"; the
+default for alsa is "default".
+.TP
+.B mix_control <mixer ctrl>
+This specifies which mixer control to use (sometimes referred to as the
+"device"). Examples of mixer controls are PCM, Line1, Master, etc. An example
+for OSS is "Pcm", and an example for alsa is "PCM".
+.TP
.B use_mmap <yes or no>
Setting this allows you to use memory-mapped I/O. Certain hardware setups may
benefit from this, but most do not. Most users do not need to set this. The
diff --git a/doc/mpdconf.example b/doc/mpdconf.example
index 2de2e05e5..0b8004237 100644
--- a/doc/mpdconf.example
+++ b/doc/mpdconf.example
@@ -162,6 +162,8 @@ log_file "~/.mpd/log"
# name "My ALSA Device"
# device "hw:0,0" # optional
# format "44100:16:2" # optional
+# mix_device "default" # optional
+# mix_control "PCM" # optional
#}
#
# An example of an OSS output:
@@ -171,6 +173,8 @@ log_file "~/.mpd/log"
# name "My OSS Device"
# device "/dev/dsp" # optional
# format "44100:16:2" # optional
+# mix_device "/dev/mixer" # optional
+# mix_control "PCM" # optional
#}
#
# An example of a shout output (for streaming to Icecast):
@@ -232,17 +236,9 @@ log_file "~/.mpd/log"
# specified it may be autodetected at startup, depending on the dependencies
# which were compiled into the server.
#
-# An example for controlling an ALSA mixer:
+# An example for controlling an ALSA or OSS mixer:
#
-#mixer_type "alsa"
-#mixer_device "default"
-#mixer_control "PCM"
-#
-# An example for controlling an OSS mixer:
-#
-#mixer_type "oss"
-#mixer_device "/dev/mixer"
-#mixer_control "PCM"
+#mixer_type "hardware"
#
# This example is a general volume control mixer, it is used to adjust the
# volume of the audio sent to the audio output, and will work with all outputs.
diff --git a/src/Makefile.am b/src/Makefile.am
index 2cd3f3924..1c18bc4a1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -273,6 +273,7 @@ endif
if HAVE_ALSA
mpd_SOURCES += output/alsa_plugin.c
+mpd_SOURCES += mixer/alsa_mixer.c
endif
if HAVE_AO
@@ -293,6 +294,7 @@ endif
if HAVE_OSS
mpd_SOURCES += output/oss_plugin.c
+mpd_SOURCES += mixer/oss_mixer.c
endif
if HAVE_OSX
@@ -315,7 +317,6 @@ if HAVE_SHOUT_OGG
mpd_SOURCES += output/shout_ogg.c
endif
-
mpd_CFLAGS = $(MPD_CFLAGS)
mpd_CPPFLAGS = \
$(CURL_CFLAGS) \
diff --git a/src/audio.c b/src/audio.c
index 0567b6452..17e63416a 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -25,6 +25,7 @@
#include "client.h"
#include "idle.h"
#include "utils.h"
+#include "mixer.h"
#include <glib.h>
@@ -428,3 +429,57 @@ errline:
}
}
+bool mixer_control_setvol(unsigned int device, int volume, int rel)
+{
+ struct audio_output *output;
+ if (device >= audioOutputArraySize)
+ return false;
+
+ output = &audioOutputArray[device];
+ if (output->plugin && output->plugin->control) {
+ if (rel) {
+ int cur_volume;
+ if (!output->plugin->control(output->data, AC_MIXER_GETVOL, &cur_volume)) {
+ return false;
+ }
+ volume = volume + cur_volume;
+ }
+ if (volume > 100)
+ volume = 100;
+ else if (volume < 0)
+ volume = 0;
+
+ return output->plugin->control(output->data, AC_MIXER_SETVOL, &volume);
+ }
+ return false;
+}
+
+bool mixer_control_getvol(unsigned int device, int *volume)
+{
+ struct audio_output *output;
+ if (device >= audioOutputArraySize)
+ return false;
+
+ output = &audioOutputArray[device];
+ if (output->plugin && output->plugin->control) {
+ return output->plugin->control(output->data, AC_MIXER_GETVOL, volume);
+ }
+ return false;
+}
+
+bool mixer_configure_legacy(char *name, ConfigParam *param)
+{
+ unsigned i;
+ struct audio_output *output;
+
+ for (i = 0; i < audioOutputArraySize; ++i) {
+ output = &audioOutputArray[i];
+ if (output && output->plugin && !strcmp(name, output->plugin->name)) {
+ if (output->plugin->control) {
+ g_debug("reconfiguring %s mixer\n", name);
+ return output->plugin->control(output->data, AC_MIXER_CONFIGURE, param);
+ }
+ }
+ }
+ return false;
+}
diff --git a/src/audio.h b/src/audio.h
index 98c377b7e..c83794689 100644
--- a/src/audio.h
+++ b/src/audio.h
@@ -21,6 +21,7 @@
#include <stdbool.h>
#include <stdio.h>
+#include "conf.h"
#define AUDIO_AO_DRIVER_DEFAULT "default"
@@ -70,4 +71,8 @@ void readAudioDevicesState(FILE *fp);
void saveAudioDevicesState(FILE *fp);
+bool mixer_control_setvol(unsigned int device, int volume, int rel);
+bool mixer_control_getvol(unsigned int device, int *volume);
+bool mixer_configure_legacy(char *name, ConfigParam *param);
+
#endif
diff --git a/src/command.c b/src/command.c
index 3929978ba..a8dc6284b 100644
--- a/src/command.c
+++ b/src/command.c
@@ -388,7 +388,7 @@ handle_status(struct client *client,
COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
COMMAND_STATUS_CROSSFADE ": %i\n"
COMMAND_STATUS_STATE ": %s\n",
- getVolumeLevel(),
+ volume_level_get(),
getPlaylistRepeatStatus(),
getPlaylistRandomStatus(),
getPlaylistVersion(),
@@ -906,7 +906,7 @@ handle_volume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &change, argv[1], need_integer))
return COMMAND_RETURN_ERROR;
- ret = changeVolumeLevel(change, 1);
+ ret = volume_level_change(change, 1);
if (ret == -1)
command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume");
@@ -922,7 +922,7 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &level, argv[1], need_integer))
return COMMAND_RETURN_ERROR;
- ret = changeVolumeLevel(level, 0);
+ ret = volume_level_change(level, 0);
if (ret == -1)
command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume");
diff --git a/src/main.c b/src/main.c
index 23bff7c8a..8ab0b3630 100644
--- a/src/main.c
+++ b/src/main.c
@@ -264,7 +264,7 @@ int main(int argc, char *argv[])
dc_init();
initAudioConfig();
initAudioDriver();
- initVolume();
+ volume_init();
client_manager_init();
replay_gain_global_init();
initNormalization();
@@ -278,7 +278,6 @@ int main(int argc, char *argv[])
initZeroconf();
- openVolumeDevice();
decoder_thread_start();
player_create();
read_state_file();
@@ -315,7 +314,7 @@ int main(int argc, char *argv[])
finishNormalization();
finishAudioDriver();
finishAudioConfig();
- finishVolume();
+ volume_finish();
mapper_finish();
path_global_finish();
finishPermissions();
diff --git a/src/mixer.h b/src/mixer.h
new file mode 100644
index 000000000..43dc3299d
--- /dev/null
+++ b/src/mixer.h
@@ -0,0 +1,33 @@
+
+#ifndef MPD_MIXER_H
+#define MPD_MIXER_H
+
+#include "conf.h"
+
+/**
+ * alsa mixer
+ */
+
+struct alsa_mixer;
+
+struct alsa_mixer *alsa_mixer_init(void);
+void alsa_mixer_finish(struct alsa_mixer *am);
+void alsa_mixer_configure(struct alsa_mixer *am, ConfigParam *param);
+bool alsa_mixer_open(struct alsa_mixer *am);
+bool alsa_mixer_control(struct alsa_mixer *am, int cmd, void *arg);
+void alsa_mixer_close(struct alsa_mixer *am);
+
+/**
+ * oss mixer
+ */
+
+struct oss_mixer;
+
+struct oss_mixer *oss_mixer_init(void);
+void oss_mixer_finish(struct oss_mixer *am);
+void oss_mixer_configure(struct oss_mixer *am, ConfigParam *param);
+bool oss_mixer_open(struct oss_mixer *am);
+bool oss_mixer_control(struct oss_mixer *am, int cmd, void *arg);
+void oss_mixer_close(struct oss_mixer *am);
+
+#endif
diff --git a/src/mixer/alsa_mixer.c b/src/mixer/alsa_mixer.c
new file mode 100644
index 000000000..506daf794
--- /dev/null
+++ b/src/mixer/alsa_mixer.c
@@ -0,0 +1,206 @@
+
+#include "../output_api.h"
+#include "../mixer.h"
+
+#include <glib.h>
+#include <alsa/asoundlib.h>
+
+#define VOLUME_MIXER_ALSA_DEFAULT "default"
+#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
+
+struct alsa_mixer {
+ char *device;
+ char *control;
+ snd_mixer_t *handle;
+ snd_mixer_elem_t *elem;
+ long volume_min;
+ long volume_max;
+ int volume_set;
+};
+
+struct alsa_mixer *
+alsa_mixer_init(void)
+{
+ struct alsa_mixer *am = g_malloc(sizeof(struct alsa_mixer));
+ am->device = NULL;
+ am->control = NULL;
+ am->handle = NULL;
+ am->elem = NULL;
+ am->volume_min = 0;
+ am->volume_max = 0;
+ am->volume_set = -1;
+ return am;
+}
+
+void
+alsa_mixer_finish(struct alsa_mixer *am)
+{
+ g_free(am);
+}
+
+void
+alsa_mixer_configure(struct alsa_mixer *am, ConfigParam *param)
+{
+ BlockParam *bp;
+
+ if ((bp = getBlockParam(param, "mix_device")))
+ am->device = bp->value;
+ if ((bp = getBlockParam(param, "mix_control")))
+ am->control = bp->value;
+}
+
+void
+alsa_mixer_close(struct alsa_mixer *am)
+{
+ if (am->handle) snd_mixer_close(am->handle);
+ am->handle = NULL;
+}
+
+bool
+alsa_mixer_open(struct alsa_mixer *am)
+{
+ int err;
+ snd_mixer_elem_t *elem;
+ const char *control_name = VOLUME_MIXER_ALSA_CONTROL_DEFAULT;
+ const char *device = VOLUME_MIXER_ALSA_DEFAULT;
+
+ if (am->device) {
+ device = am->device;
+ }
+ err = snd_mixer_open(&am->handle, 0);
+ snd_config_update_free_global();
+ if (err < 0) {
+ g_warning("problems opening alsa mixer: %s\n", snd_strerror(err));
+ return false;
+ }
+
+ if ((err = snd_mixer_attach(am->handle, device)) < 0) {
+ g_warning("problems attaching alsa mixer: %s\n",
+ snd_strerror(err));
+ alsa_mixer_close(am);
+ return false;
+ }
+
+ if ((err = snd_mixer_selem_register(am->handle, NULL,
+ NULL)) < 0) {
+ g_warning("problems snd_mixer_selem_register'ing: %s\n",
+ snd_strerror(err));
+ alsa_mixer_close(am);
+ return false;
+ }
+
+ if ((err = snd_mixer_load(am->handle)) < 0) {
+ g_warning("problems snd_mixer_selem_register'ing: %s\n",
+ snd_strerror(err));
+ alsa_mixer_close(am);
+ return false;
+ }
+
+ elem = snd_mixer_first_elem(am->handle);
+
+ if (am->control) {
+ control_name = am->control;
+ }
+
+ while (elem) {
+ if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) {
+ if (strcasecmp(control_name,
+ snd_mixer_selem_get_name(elem)) == 0) {
+ break;
+ }
+ }
+ elem = snd_mixer_elem_next(elem);
+ }
+
+ if (elem) {
+ am->elem = elem;
+ snd_mixer_selem_get_playback_volume_range(am->elem,
+ &am->volume_min,
+ &am->volume_max);
+ return true;
+ }
+
+ g_warning("can't find alsa mixer control \"%s\"\n", control_name);
+
+ alsa_mixer_close(am);
+ return false;
+}
+
+bool
+alsa_mixer_control(struct alsa_mixer *am, int cmd, void *arg)
+{
+ switch (cmd) {
+ case AC_MIXER_CONFIGURE:
+ alsa_mixer_configure(am, (ConfigParam *)arg);
+ if (am->handle)
+ alsa_mixer_close(am);
+ return true;
+ case AC_MIXER_GETVOL:
+ {
+ int err;
+ int ret, *volume = arg;
+ long level;
+
+ if (!am->handle && !alsa_mixer_open(am)) {
+ return false;
+ }
+ if ((err = snd_mixer_handle_events(am->handle)) < 0) {
+ g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
+ snd_strerror(err), "handle_events");
+ alsa_mixer_close(am);
+ return false;
+ }
+ if ((err = snd_mixer_selem_get_playback_volume(am->elem,
+ SND_MIXER_SCHN_FRONT_LEFT, &level)) < 0) {
+ g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
+ snd_strerror(err), "selem_get_playback_volume");
+ alsa_mixer_close(am);
+ return false;
+ }
+ ret = ((am->volume_set / 100.0) * (am->volume_max - am->volume_min)
+ + am->volume_min) + 0.5;
+ if (am->volume_set > 0 && ret == level) {
+ ret = am->volume_set;
+ } else {
+ ret = (int)(100 * (((float)(level - am->volume_min)) /
+ (am->volume_max - am->volume_min)) + 0.5);
+ }
+ *volume = ret;
+ return true;
+ }
+ case AC_MIXER_SETVOL:
+ {
+ float vol;
+ long level;
+ int *volume = arg;
+ int err;
+
+ if (!am->handle && !alsa_mixer_open(am)) {
+ return false;
+ }
+ vol = *volume;
+
+ am->volume_set = vol + 0.5;
+ am->volume_set = am->volume_set > 100 ? 100 :
+ (am->volume_set < 0 ? 0 : am->volume_set);
+
+ level = (long)(((vol / 100.0) * (am->volume_max - am->volume_min) +
+ am->volume_min) + 0.5);
+ level = level > am->volume_max ? am->volume_max : level;
+ level = level < am->volume_min ? am->volume_min : level;
+
+ if ((err = snd_mixer_selem_set_playback_volume_all(am->elem,
+ level)) < 0) {
+ g_warning("problems setting alsa volume: %s\n",
+ snd_strerror(err));
+ alsa_mixer_close(am);
+ return false;
+ }
+ return true;
+ }
+ default:
+ g_warning("Unsuported alsa control\n");
+ break;
+ }
+ return false;
+}
diff --git a/src/mixer/oss_mixer.c b/src/mixer/oss_mixer.c
new file mode 100644
index 000000000..ca4027f75
--- /dev/null
+++ b/src/mixer/oss_mixer.c
@@ -0,0 +1,197 @@
+
+#include <glib.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "../output_api.h"
+#include "../mixer.h"
+
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+# include <soundcard.h>
+#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+# include <sys/soundcard.h>
+#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+
+#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer"
+
+struct oss_mixer {
+ const char *device;
+ const char *control;
+ int device_fd;
+ int volume_control;
+};
+
+struct oss_mixer *oss_mixer_init(void);
+void oss_mixer_finish(struct oss_mixer *am);
+void oss_mixer_configure(struct oss_mixer *am, ConfigParam *param);
+bool oss_mixer_open(struct oss_mixer *am);
+bool oss_mixer_control(struct oss_mixer *am, int cmd, void *arg);
+void oss_mixer_close(struct oss_mixer *am);
+
+struct oss_mixer *
+oss_mixer_init(void)
+{
+ struct oss_mixer *om = g_malloc(sizeof(struct oss_mixer));
+ om->device = NULL;
+ om->control = NULL;
+ om->device_fd = -1;
+ om->volume_control = SOUND_MIXER_PCM;
+ return om;
+}
+
+void
+oss_mixer_finish(struct oss_mixer *om)
+{
+ g_free(om);
+}
+
+void
+oss_mixer_configure(struct oss_mixer *om, ConfigParam *param)
+{
+ BlockParam *bp;
+ bp = getBlockParam(param, "mix_device");
+ if (bp) {
+ om->device = bp->value;
+ }
+ bp = getBlockParam(param, "mix_control");
+ if (bp) {
+ om->control = bp->value;
+ }
+}
+
+void
+oss_mixer_close(struct oss_mixer *om)
+{
+ if (om->device_fd != -1)
+ while (close(om->device_fd) && errno == EINTR) ;
+ om->device_fd = -1;
+}
+
+static int
+oss_find_mixer(const char *name)
+{
+ const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
+ size_t name_length = strlen(name);
+
+ for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
+ if (strncasecmp(name, labels[i], name_length) == 0 &&
+ (labels[i][name_length] == 0 ||
+ labels[i][name_length] == ' '))
+ return i;
+ }
+ return -1;
+}
+
+bool
+oss_mixer_open(struct oss_mixer *om)
+{
+ const char *device = VOLUME_MIXER_OSS_DEFAULT;
+
+ if (om->device) {
+ device = om->device;
+ }
+
+ if ((om->device_fd = open(device, O_RDONLY)) < 0) {
+ g_warning("Unable to open oss mixer \"%s\"\n", device);
+ return false;
+ }
+
+ if (om->control) {
+ int i;
+ int devmask = 0;
+
+ if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
+ g_warning("errors getting read_devmask for oss mixer\n");
+ oss_mixer_close(om);
+ return false;
+ }
+ i = oss_find_mixer(om->control);
+
+ if (i < 0) {
+ g_warning("mixer control \"%s\" not found\n",
+ om->control);
+ oss_mixer_close(om);
+ return false;
+ } else if (!((1 << i) & devmask)) {
+ g_warning("mixer control \"%s\" not usable\n",
+ om->control);
+ oss_mixer_close(om);
+ return false;
+ }
+ om->volume_control = i;
+ }
+ return true;
+}
+
+bool
+oss_mixer_control(struct oss_mixer *om, int cmd, void *arg)
+{
+ switch (cmd) {
+ case AC_MIXER_CONFIGURE:
+ oss_mixer_configure(om, (ConfigParam *)arg);
+ //if (om->device_fd >= 0)
+ oss_mixer_close(om);
+ return true;
+ break;
+ case AC_MIXER_GETVOL:
+ {
+ int left, right, level;
+ int *ret;
+
+ if (om->device_fd < 0 && !oss_mixer_open(om)) {
+ return false;
+ }
+
+ if (ioctl(om->device_fd, MIXER_READ(om->volume_control), &level) < 0) {
+ oss_mixer_close(om);
+ g_warning("unable to read oss volume\n");
+ return false;
+ }
+
+ left = level & 0xff;
+ right = (level & 0xff00) >> 8;
+
+ if (left != right) {
+ g_warning("volume for left and right is not the same, \"%i\" and "
+ "\"%i\"\n", left, right);
+ }
+ ret = (int *) arg;
+ *ret = left;
+ return true;
+ }
+ case AC_MIXER_SETVOL:
+ {
+ int new;
+ int level;
+ int *value = arg;
+
+ if (om->device_fd < 0 && !oss_mixer_open(om)) {
+ return false;
+ }
+
+ new = *value;
+ if (new < 0) {
+ new = 0;
+ } else if (new > 100) {
+ new = 100;
+ }
+
+ level = (new << 8) + new;
+
+ if (ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level) < 0) {
+ g_warning("unable to set oss volume\n");
+ oss_mixer_close(om);
+ return false;
+ }
+ return true;
+ }
+ default:
+ g_warning("Unsuported oss control\n");
+ break;
+ }
+ return false;
+}
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c
index 57d102c51..5f53729a7 100644
--- a/src/output/alsa_plugin.c
+++ b/src/output/alsa_plugin.c
@@ -18,6 +18,7 @@
#include "../output_api.h"
#include "../utils.h"
+#include "../mixer.h"
#include <glib.h>
#include <alsa/asoundlib.h>
@@ -51,6 +52,9 @@ typedef struct _AlsaData {
unsigned int period_time;
int sampleSize;
int useMmap;
+
+ struct alsa_mixer *mixer;
+
} AlsaData;
static const char *
@@ -71,12 +75,15 @@ static AlsaData *newAlsaData(void)
ret->buffer_time = MPD_ALSA_BUFFER_TIME_US;
ret->period_time = 0;
+ ret->mixer = alsa_mixer_init();
+
return ret;
}
static void freeAlsaData(AlsaData * ad)
{
g_free(ad->device);
+ alsa_mixer_finish(ad->mixer);
free(ad);
}
@@ -125,8 +132,10 @@ static void *alsa_initDriver(mpd_unused struct audio_output *ao,
free_global_registered = 1;
}
- if (param)
+ if (param) {
alsa_configure(ad, param);
+ alsa_mixer_configure(ad->mixer, param);
+ }
return ad;
}
@@ -181,6 +190,8 @@ static bool alsa_openDevice(void *data, struct audio_format *audioFormat)
unsigned int period_time, period_time_ro;
unsigned int buffer_time;
+ alsa_mixer_open(ad->mixer);
+
if ((bitformat = get_bitformat(audioFormat)) == SND_PCM_FORMAT_UNKNOWN)
g_warning("ALSA device \"%s\" doesn't support %u bit audio\n",
alsa_device(ad), audioFormat->bits);
@@ -403,6 +414,7 @@ static void alsa_closeDevice(void *data)
snd_pcm_close(ad->pcmHandle);
ad->pcmHandle = NULL;
}
+ alsa_mixer_close(ad->mixer);
}
static bool
@@ -436,6 +448,13 @@ alsa_playAudio(void *data, const char *playChunk, size_t size)
return true;
}
+static bool
+alsa_control(void *data, int cmd, void *arg)
+{
+ AlsaData *ad = data;
+ return alsa_mixer_control(ad->mixer, cmd, arg);
+}
+
const struct audio_output_plugin alsaPlugin = {
.name = "alsa",
.test_default_device = alsa_testDefault,
@@ -445,4 +464,5 @@ const struct audio_output_plugin alsaPlugin = {
.play = alsa_playAudio,
.cancel = alsa_dropBufferedAudio,
.close = alsa_closeDevice,
+ .control = alsa_control
};
diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c
index 4727823c3..1f582b2b4 100644
--- a/src/output/oss_plugin.c
+++ b/src/output/oss_plugin.c
@@ -20,6 +20,7 @@
*/
#include "../output_api.h"
+#include "../mixer.h"
#include <glib.h>
#include <sys/stat.h>
@@ -53,6 +54,7 @@ typedef struct _OssData {
int numSupported[3];
int *unsupported[3];
int numUnsupported[3];
+ struct oss_mixer *mixer;
} OssData;
enum oss_support {
@@ -273,6 +275,8 @@ static OssData *newOssData(void)
supportParam(ret, SNDCTL_DSP_CHANNELS, 2);
supportParam(ret, SNDCTL_DSP_SAMPLESIZE, 16);
+ ret->mixer = oss_mixer_init();
+
return ret;
}
@@ -285,6 +289,8 @@ static void freeOssData(OssData * od)
g_free(od->unsupported[OSS_CHANNELS]);
g_free(od->unsupported[OSS_BITS]);
+ oss_mixer_finish(od->mixer);
+
free(od);
}
@@ -348,6 +354,7 @@ static void *oss_open_default(ConfigParam *param)
if (ret[i] == 0) {
OssData *od = newOssData();
od->device = default_devices[i];
+ oss_mixer_configure(od->mixer, param);
return od;
}
}
@@ -388,6 +395,7 @@ static void *oss_initDriver(mpd_unused struct audio_output *audioOutput,
if (bp) {
OssData *od = newOssData();
od->device = bp->value;
+ oss_mixer_configure(od->mixer, param);
return od;
}
}
@@ -513,6 +521,8 @@ oss_openDevice(void *data, struct audio_format *audioFormat)
od->audio_format.bits, od->audio_format.channels,
od->audio_format.sample_rate);
+ oss_mixer_open(od->mixer);
+
return ret;
}
@@ -521,6 +531,7 @@ static void oss_closeDevice(void *data)
OssData *od = data;
oss_close(od);
+ oss_mixer_close(od->mixer);
}
static void oss_dropBufferedAudio(void *data)
@@ -559,6 +570,13 @@ oss_playAudio(void *data, const char *playChunk, size_t size)
return true;
}
+static bool
+oss_control(void *data, int cmd, void *arg)
+{
+ OssData *od = data;
+ return oss_mixer_control(od->mixer, cmd, arg);
+}
+
const struct audio_output_plugin ossPlugin = {
.name = "oss",
.test_default_device = oss_testDefault,
@@ -568,4 +586,5 @@ const struct audio_output_plugin ossPlugin = {
.play = oss_playAudio,
.cancel = oss_dropBufferedAudio,
.close = oss_closeDevice,
+ .control = oss_control,
};
diff --git a/src/output_api.h b/src/output_api.h
index 7cefea77e..fb4b096a1 100644
--- a/src/output_api.h
+++ b/src/output_api.h
@@ -101,6 +101,12 @@ struct audio_output_plugin {
void (*close)(void *data);
/**
+ * Control the device. Usualy used for implementing
+ * set and get mixer levels
+ */
+ bool (*control)(void *data, int cmd, void *arg);
+
+ /**
* Display metadata for the next chunk. Optional method,
* because not all devices can display metadata.
*/
@@ -118,6 +124,12 @@ enum audio_output_command {
AO_COMMAND_KILL
};
+enum audio_control_command {
+ AC_MIXER_GETVOL = 0,
+ AC_MIXER_SETVOL,
+ AC_MIXER_CONFIGURE,
+};
+
struct audio_output;
const char *audio_output_get_name(const struct audio_output *ao);
diff --git a/src/volume.c b/src/volume.c
index 01976888d..e41d1d0e1 100644
--- a/src/volume.c
+++ b/src/volume.c
@@ -15,475 +15,141 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-
#include "volume.h"
+
#include "conf.h"
#include "player_control.h"
#include "utils.h"
#include "idle.h"
#include "pcm_utils.h"
#include "config.h"
+#include "audio.h"
#include <glib.h>
-
#include <math.h>
#include <string.h>
-#ifdef HAVE_OSS
-#include <sys/ioctl.h>
-#include <sys/soundcard.h>
-#endif
-#ifdef HAVE_ALSA
-#include <alsa/asoundlib.h>
-#endif
-
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "volume"
#define VOLUME_MIXER_TYPE_SOFTWARE 0
-#define VOLUME_MIXER_TYPE_OSS 1
-#define VOLUME_MIXER_TYPE_ALSA 2
+#define VOLUME_MIXER_TYPE_HARDWARE 1
#define VOLUME_MIXER_SOFTWARE_DEFAULT ""
-#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer"
-#define VOLUME_MIXER_ALSA_DEFAULT "default"
-#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
#define SW_VOLUME_STATE "sw_volume: "
-#ifdef HAVE_OSS
-#define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_OSS
-#define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_OSS_DEFAULT
-#else
-#ifdef HAVE_ALSA
-#define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_ALSA
-#define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_ALSA_DEFAULT
-#else
-#define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_SOFTWARE
-#define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_SOFTWARE_DEFAULT
-#endif
-#endif
-
-static int volume_mixerType = VOLUME_MIXER_TYPE_DEFAULT;
-static const char *volume_mixerDevice = VOLUME_MIXER_DEVICE_DEFAULT;
-
-static int volume_softwareSet = 100;
-
-#ifdef HAVE_OSS
-static int volume_ossFd = -1;
-static int volume_ossControl = SOUND_MIXER_PCM;
-#endif
-
-#ifdef HAVE_ALSA
-static snd_mixer_t *volume_alsaMixerHandle;
-static snd_mixer_elem_t *volume_alsaElem;
-static long volume_alsaMin;
-static long volume_alsaMax;
-static int volume_alsaSet = -1;
-#endif
-
-#ifdef HAVE_OSS
-
-static void closeOssMixer(void)
-{
- while (close(volume_ossFd) && errno == EINTR) ;
- volume_ossFd = -1;
-}
+const struct audio_output_plugin *default_mixer;
+static int volume_mixer_type = VOLUME_MIXER_TYPE_HARDWARE;
+static int volume_software_set = 100;
-static int
-oss_find_mixer(const char *name)
+void volume_finish(void)
{
- const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
- size_t name_length = strlen(name);
-
- for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
- if (strncasecmp(name, labels[i], name_length) == 0 &&
- (labels[i][name_length] == 0 ||
- labels[i][name_length] == ' '))
- return i;
- }
-
- return -1;
}
-static int prepOssMixer(const char *device)
+static void
+mixer_reconfigure(char *driver)
{
- ConfigParam *param;
+ ConfigParam *newparam, *param;
- if ((volume_ossFd = open(device, O_RDONLY)) < 0) {
- g_warning("unable to open oss mixer \"%s\"", device);
- return -1;
- }
+ //create parameter list
+ newparam = newConfigParam(NULL, -1);
- if ((param = getConfigParam(CONF_MIXER_CONTROL))) {
- int i;
- int devmask = 0;
-
- if (ioctl(volume_ossFd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
- g_warning("errors getting read_devmask for oss mixer");
- closeOssMixer();
- return -1;
- }
-
- i = oss_find_mixer(param->value);
-
- if (i < 0) {
- g_warning("mixer control \"%s\" not found at line %i",
- param->value, param->line);
- closeOssMixer();
- return -1;
- } else if (!((1 << i) & devmask)) {
- g_warning("mixer control \"%s\" not usable at line %i",
- param->value, param->line);
- closeOssMixer();
- return -1;
- }
-
- volume_ossControl = i;
- }
-
- return 0;
-}
-
-static int ensure_oss_open(void)
-{
- if ((volume_ossFd < 0 && prepOssMixer(volume_mixerDevice) < 0))
- return -1;
- return 0;
-}
-
-static int getOssVolumeLevel(void)
-{
- int left, right, level;
-
- if (ensure_oss_open() < 0)
- return -1;
-
- if (ioctl(volume_ossFd, MIXER_READ(volume_ossControl), &level) < 0) {
- closeOssMixer();
- g_warning("unable to read volume");
- return -1;
- }
-
- left = level & 0xff;
- right = (level & 0xff00) >> 8;
-
- if (left != right) {
- g_warning("volume for left and right is not the same, \"%i\" "
- "and \"%i\"", left, right);
- }
-
- return left;
-}
-
-static int changeOssVolumeLevel(int change, int rel)
-{
- int current;
- int new;
- int level;
-
- if (rel) {
- if ((current = getOssVolumeLevel()) < 0)
- return -1;
-
- new = current + change;
- } else {
- if (ensure_oss_open() < 0)
- return -1;
- new = change;
- }
-
- if (new < 0)
- new = 0;
- else if (new > 100)
- new = 100;
-
- level = (new << 8) + new;
-
- if (ioctl(volume_ossFd, MIXER_WRITE(volume_ossControl), &level) < 0) {
- closeOssMixer();
- return -1;
- }
-
- return 0;
-}
-#endif
-
-#ifdef HAVE_ALSA
-static void closeAlsaMixer(void)
-{
- snd_mixer_close(volume_alsaMixerHandle);
- volume_alsaMixerHandle = NULL;
-}
-
-static int prepAlsaMixer(const char *card)
-{
- int err;
- snd_mixer_elem_t *elem;
- const char *controlName = VOLUME_MIXER_ALSA_CONTROL_DEFAULT;
- ConfigParam *param;
-
- err = snd_mixer_open(&volume_alsaMixerHandle, 0);
- snd_config_update_free_global();
- if (err < 0) {
- g_warning("problems opening alsa mixer: %s",
- snd_strerror(err));
- return -1;
- }
-
- if ((err = snd_mixer_attach(volume_alsaMixerHandle, card)) < 0) {
- closeAlsaMixer();
- g_warning("problems attaching alsa mixer: %s",
- snd_strerror(err));
- return -1;
- }
-
- if ((err =
- snd_mixer_selem_register(volume_alsaMixerHandle, NULL,
- NULL)) < 0) {
- closeAlsaMixer();
- g_warning("problems snd_mixer_selem_register'ing: %s",
- snd_strerror(err));
- return -1;
- }
-
- if ((err = snd_mixer_load(volume_alsaMixerHandle)) < 0) {
- closeAlsaMixer();
- g_warning("problems snd_mixer_selem_register'ing: %s",
- snd_strerror(err));
- return -1;
+ param = getConfigParam(CONF_MIXER_DEVICE);
+ if (param) {
+ g_warning("deprecated option mixer_device found, translating to %s config section\n", driver);
+ addBlockParam(newparam, "mix_device", param->value, -1);
}
-
- elem = snd_mixer_first_elem(volume_alsaMixerHandle);
-
param = getConfigParam(CONF_MIXER_CONTROL);
-
if (param) {
- controlName = param->value;
+ g_warning("deprecated option mixer_control found, translating to %s config section\n", driver);
+ addBlockParam(newparam, "mix_control", param->value, -1);
}
-
- while (elem) {
- if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) {
- if (strcasecmp(controlName,
- snd_mixer_selem_get_name(elem)) == 0) {
- break;
- }
+ if (newparam->numberOfBlockParams > 0) {
+ //call configure method of corrensponding mixer
+ if (!mixer_configure_legacy(driver, newparam)) {
+ g_error("Using mixer_type '%s' with not enabled %s output", driver, driver);
}
- elem = snd_mixer_elem_next(elem);
- }
-
- if (elem) {
- volume_alsaElem = elem;
- snd_mixer_selem_get_playback_volume_range(volume_alsaElem,
- &volume_alsaMin,
- &volume_alsaMax);
- return 0;
}
-
- g_warning("can't find alsa mixer_control \"%s\"", controlName);
-
- closeAlsaMixer();
- return -1;
}
-static int prep_alsa_get_level(long *level)
-{
- const char *cmd;
- int err;
-
- if (!volume_alsaMixerHandle && prepAlsaMixer(volume_mixerDevice) < 0)
- return -1;
-
- if ((err = snd_mixer_handle_events(volume_alsaMixerHandle)) < 0) {
- cmd = "handle_events";
- goto error;
- }
- if ((err = snd_mixer_selem_get_playback_volume(volume_alsaElem,
- SND_MIXER_SCHN_FRONT_LEFT,
- level)) < 0) {
- cmd = "selem_get_playback_volume";
- goto error;
- }
- return 0;
-
-error:
- g_warning("problems getting alsa volume: %s (snd_mixer_%s)",
- snd_strerror(err), cmd);
- closeAlsaMixer();
- return -1;
-}
-
-static int getAlsaVolumeLevel(void)
-{
- int ret;
- long level;
- long max = volume_alsaMax;
- long min = volume_alsaMin;
-
- if (prep_alsa_get_level(&level) < 0)
- return -1;
-
- ret = ((volume_alsaSet / 100.0) * (max - min) + min) + 0.5;
- if (volume_alsaSet > 0 && ret == level) {
- ret = volume_alsaSet;
- } else
- ret = (int)(100 * (((float)(level - min)) / (max - min)) + 0.5);
-
- return ret;
-}
-
-static int changeAlsaVolumeLevel(int change, int rel)
-{
- float vol;
- long level;
- long test;
- long max = volume_alsaMax;
- long min = volume_alsaMin;
- int err;
-
- if (prep_alsa_get_level(&level) < 0)
- return -1;
-
- if (rel) {
- test = ((volume_alsaSet / 100.0) * (max - min) + min) + 0.5;
- if (volume_alsaSet >= 0 && level == test) {
- vol = volume_alsaSet;
- } else
- vol = 100.0 * (((float)(level - min)) / (max - min));
- vol += change;
- } else
- vol = change;
-
- volume_alsaSet = vol + 0.5;
- volume_alsaSet = volume_alsaSet > 100 ? 100 :
- (volume_alsaSet < 0 ? 0 : volume_alsaSet);
-
- level = (long)(((vol / 100.0) * (max - min) + min) + 0.5);
- level = level > max ? max : level;
- level = level < min ? min : level;
-
- if ((err =
- snd_mixer_selem_set_playback_volume_all(volume_alsaElem,
- level)) < 0) {
- g_warning("problems setting alsa volume: %s",
- snd_strerror(err));
- closeAlsaMixer();
- return -1;
- }
-
- return 0;
-}
-#endif
-
-static int prepMixer(const char *device)
-{
- switch (volume_mixerType) {
-#ifdef HAVE_ALSA
- case VOLUME_MIXER_TYPE_ALSA:
- return prepAlsaMixer(device);
-#endif
-#ifdef HAVE_OSS
- case VOLUME_MIXER_TYPE_OSS:
- return prepOssMixer(device);
-#endif
- }
-
- return 0;
-}
-
-void finishVolume(void)
-{
- switch (volume_mixerType) {
-#ifdef HAVE_ALSA
- case VOLUME_MIXER_TYPE_ALSA:
- closeAlsaMixer();
- break;
-#endif
-#ifdef HAVE_OSS
- case VOLUME_MIXER_TYPE_OSS:
- closeOssMixer();
- break;
-#endif
- }
-}
-
-void initVolume(void)
+void volume_init(void)
{
ConfigParam *param = getConfigParam(CONF_MIXER_TYPE);
-
+ //hw mixing is by default
if (param) {
- if (0) ;
-#ifdef HAVE_ALSA
- else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) {
- volume_mixerType = VOLUME_MIXER_TYPE_ALSA;
- volume_mixerDevice = VOLUME_MIXER_ALSA_DEFAULT;
- }
-#endif
-#ifdef HAVE_OSS
- else if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) {
- volume_mixerType = VOLUME_MIXER_TYPE_OSS;
- volume_mixerDevice = VOLUME_MIXER_OSS_DEFAULT;
- }
-#endif
- else if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) {
- volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
- volume_mixerDevice = VOLUME_MIXER_SOFTWARE_DEFAULT;
+ if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) {
+ volume_mixer_type = VOLUME_MIXER_TYPE_SOFTWARE;
+ } else if (strcmp(param->value, VOLUME_MIXER_HARDWARE) == 0) {
+ //nothing to do
} else {
- g_error("unknown mixer type %s at line %i",
- param->value, param->line);
+ //fallback to old config behaviour
+ if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) {
+ mixer_reconfigure(param->value);
+ } else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) {
+ mixer_reconfigure(param->value);
+ } else {
+ g_error("unknown mixer type %s at line %i\n",
+ param->value, param->line);
+ }
}
}
-
- param = getConfigParam(CONF_MIXER_DEVICE);
-
- if (param) {
- volume_mixerDevice = param->value;
- }
}
-void openVolumeDevice(void)
+static int hardware_volume_get(void)
{
- if (prepMixer(volume_mixerDevice) < 0) {
- g_message("using software volume");
- volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
+ int device, count;
+ int volume, volume_total, volume_ok;
+
+ volume_total = 0;
+ volume_ok = 0;
+
+ count = audio_output_count();
+ for (device=0; device<count ;device++) {
+ if (mixer_control_getvol(device, &volume)) {
+ g_debug("device %d: volume: %d\n", device, volume);
+ volume_total += volume;
+ volume_ok++;
+ }
+ }
+ if (volume_ok > 0) {
+ //return average
+ return volume_total / volume_ok;
+ } else {
+ return -1;
}
}
-static int getSoftwareVolume(void)
+static int software_volume_get(void)
{
- return volume_softwareSet;
+ return volume_software_set;
}
-int getVolumeLevel(void)
+int volume_level_get(void)
{
- switch (volume_mixerType) {
-#ifdef HAVE_ALSA
- case VOLUME_MIXER_TYPE_ALSA:
- return getAlsaVolumeLevel();
-#endif
-#ifdef HAVE_OSS
- case VOLUME_MIXER_TYPE_OSS:
- return getOssVolumeLevel();
-#endif
+ switch (volume_mixer_type) {
case VOLUME_MIXER_TYPE_SOFTWARE:
- return getSoftwareVolume();
+ return software_volume_get();
+ case VOLUME_MIXER_TYPE_HARDWARE:
+ return hardware_volume_get();
default:
return -1;
}
+ return -1;
}
-static int changeSoftwareVolume(int change, int rel)
+static int software_volume_change(int change, int rel)
{
int new = change;
if (rel)
- new += volume_softwareSet;
+ new += volume_software_set;
if (new > 100)
new = 100;
else if (new < 0)
new = 0;
- volume_softwareSet = new;
+ volume_software_set = new;
/*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5; */
if (new >= 100)
@@ -499,21 +165,26 @@ static int changeSoftwareVolume(int change, int rel)
return 0;
}
-int changeVolumeLevel(int change, int rel)
+static int hardware_volume_change(int change, int rel)
+{
+ int device, count;
+
+ count = audio_output_count();
+ for (device=0; device<count ;device++) {
+ mixer_control_setvol(device, change, rel);
+ }
+ return 0;
+}
+
+int volume_level_change(int change, int rel)
{
idle_add(IDLE_MIXER);
- switch (volume_mixerType) {
-#ifdef HAVE_ALSA
- case VOLUME_MIXER_TYPE_ALSA:
- return changeAlsaVolumeLevel(change, rel);
-#endif
-#ifdef HAVE_OSS
- case VOLUME_MIXER_TYPE_OSS:
- return changeOssVolumeLevel(change, rel);
-#endif
+ switch (volume_mixer_type) {
+ case VOLUME_MIXER_TYPE_HARDWARE:
+ return hardware_volume_change(change, rel);
case VOLUME_MIXER_TYPE_SOFTWARE:
- return changeSoftwareVolume(change, rel);
+ return software_volume_change(change, rel);
default:
return 0;
}
@@ -525,7 +196,7 @@ void read_sw_volume_state(FILE *fp)
char *end = NULL;
long int sv;
- if (volume_mixerType != VOLUME_MIXER_TYPE_SOFTWARE)
+ if (volume_mixer_type != VOLUME_MIXER_TYPE_SOFTWARE)
return;
while (fgets(buf, sizeof(buf), fp)) {
if (!g_str_has_prefix(buf, SW_VOLUME_STATE))
@@ -534,16 +205,15 @@ void read_sw_volume_state(FILE *fp)
g_strchomp(buf);
sv = strtol(buf + strlen(SW_VOLUME_STATE), &end, 10);
if (G_LIKELY(!*end))
- changeSoftwareVolume(sv, 0);
+ software_volume_change(sv, 0);
else
- g_warning("Can't parse software volume: %s", buf);
+ g_warning("Can't parse software volume: %s\n", buf);
return;
}
}
void save_sw_volume_state(FILE *fp)
{
- if (volume_mixerType == VOLUME_MIXER_TYPE_SOFTWARE)
- fprintf(fp, SW_VOLUME_STATE "%d\n", volume_softwareSet);
+ if (volume_mixer_type == VOLUME_MIXER_TYPE_SOFTWARE)
+ fprintf(fp, SW_VOLUME_STATE "%d\n", volume_software_set);
}
-
diff --git a/src/volume.h b/src/volume.h
index 1a37e4bfd..fbf86df89 100644
--- a/src/volume.h
+++ b/src/volume.h
@@ -24,16 +24,15 @@
#define VOLUME_MIXER_OSS "oss"
#define VOLUME_MIXER_ALSA "alsa"
#define VOLUME_MIXER_SOFTWARE "software"
+#define VOLUME_MIXER_HARDWARE "hardware"
-void initVolume(void);
+void volume_init(void);
-void openVolumeDevice(void);
+void volume_finish(void);
-void finishVolume(void);
+int volume_level_get(void);
-int getVolumeLevel(void);
-
-int changeVolumeLevel(int change, int rel);
+int volume_level_change(int change, int rel);
void read_sw_volume_state(FILE *fp);