Moving mixers to audio outputs

This commit is contained in:
Viliam Mateicka 2008-12-31 16:46:41 +01:00
parent dd9af72a74
commit 9a70c4d06d
16 changed files with 686 additions and 459 deletions

1
NEWS
View File

@ -1,4 +1,5 @@
ver 0.15 - (200?/??/??)
* Rewritten mixer code to support multiple mixers
* Add audio archive extraction support:
- bzip2
- iso9660

View File

@ -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

View File

@ -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.

View File

@ -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) \

View File

@ -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;
}

View File

@ -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

View File

@ -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");

View File

@ -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();

33
src/mixer.h Normal file
View File

@ -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

206
src/mixer/alsa_mixer.c Normal file
View File

@ -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;
}

197
src/mixer/oss_mixer.c Normal file
View File

@ -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;
}

View File

@ -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
};

View File

@ -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,
};

View File

@ -100,6 +100,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);

View File

@ -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
const struct audio_output_plugin *default_mixer;
static int volume_mixer_type = VOLUME_MIXER_TYPE_HARDWARE;
static int volume_software_set = 100;
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)
void volume_finish(void)
{
while (close(volume_ossFd) && errno == EINTR) ;
volume_ossFd = -1;
}
static int
oss_find_mixer(const char *name)
static void
mixer_reconfigure(char *driver)
{
const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
size_t name_length = strlen(name);
ConfigParam *newparam, *param;
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)
{
ConfigParam *param;
if ((volume_ossFd = open(device, O_RDONLY)) < 0) {
g_warning("unable to open oss mixer \"%s\"", device);
return -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;
}
elem = snd_mixer_first_elem(volume_alsaMixerHandle);
param = getConfigParam(CONF_MIXER_CONTROL);
if (param) {
controlName = param->value;
}
while (elem) {
if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) {
if (strcasecmp(controlName,
snd_mixer_selem_get_name(elem)) == 0) {
break;
}
}
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)
{
ConfigParam *param = getConfigParam(CONF_MIXER_TYPE);
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;
} else {
g_error("unknown mixer type %s at line %i",
param->value, param->line);
}
}
//create parameter list
newparam = newConfigParam(NULL, -1);
param = getConfigParam(CONF_MIXER_DEVICE);
if (param) {
volume_mixerDevice = param->value;
g_warning("deprecated option mixer_device found, translating to %s config section\n", driver);
addBlockParam(newparam, "mix_device", param->value, -1);
}
param = getConfigParam(CONF_MIXER_CONTROL);
if (param) {
g_warning("deprecated option mixer_control found, translating to %s config section\n", driver);
addBlockParam(newparam, "mix_control", param->value, -1);
}
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);
}
}
}
void openVolumeDevice(void)
void volume_init(void)
{
if (prepMixer(volume_mixerDevice) < 0) {
g_message("using software volume");
volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
ConfigParam *param = getConfigParam(CONF_MIXER_TYPE);
//hw mixing is by default
if (param) {
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 {
//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);
}
}
}
}
static int getSoftwareVolume(void)
static int hardware_volume_get(void)
{
return volume_softwareSet;
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;
}
}
int getVolumeLevel(void)
static int software_volume_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
return volume_software_set;
}
int volume_level_get(void)
{
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);
}

View File

@ -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);