d37962fd46
The names for channels provided by <sys/soundcard.h> have a padding with space when their name is shorter than 6 characters, and this cause excluding them difficult. The new comparison code makes sure this padding is properly handled during the match search. Took the opportunity to bring light changes to try to improve the readability of the channel listing code. Signed-off-by: Christophe CURIS <christophe.curis@free.fr>
452 lines
11 KiB
C
452 lines
11 KiB
C
/* WMix 3.0 -- a mixer using the OSS mixer API.
|
|
* Copyright (C) 2000, 2001
|
|
* Daniel Richard G. <skunk@mit.edu>,
|
|
* timecop <timecop@japan.co.jp>
|
|
*
|
|
* 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/soundcard.h>
|
|
|
|
#include "include/common.h"
|
|
#include "include/misc.h"
|
|
#include "include/mixer.h"
|
|
|
|
#define WMVOLUME_CHANNEL_NAMES \
|
|
"Master volume", \
|
|
"Bass", \
|
|
"Treble", \
|
|
"FM Synth volume", \
|
|
"PCM Wave volume", \
|
|
"PC Speaker", \
|
|
"Line In level", \
|
|
"Microphone level", \
|
|
"CD volume", \
|
|
"Recording monitor", \
|
|
"PCM Wave 2 volume", \
|
|
"Recording volume", \
|
|
"Input gain", \
|
|
"Output gain", \
|
|
"Line In 1", \
|
|
"Line In 2", \
|
|
"Line In 3", \
|
|
"Digital In 1", \
|
|
"Digital In 2", \
|
|
"Digital In 3", \
|
|
"Phone input", \
|
|
"Phone output", \
|
|
"Video volume", \
|
|
"Radio volume", \
|
|
"Monitor volume"
|
|
|
|
#ifdef OSS_CHANNEL_NAMES
|
|
#define CHANNEL_NAMES SOUND_DEVICE_LABELS
|
|
#else
|
|
#define CHANNEL_NAMES WMVOLUME_CHANNEL_NAMES
|
|
#endif
|
|
|
|
typedef struct {
|
|
const char *name; /* name of channel */
|
|
const char *sname; /* short name of the channel */
|
|
int dev; /* channel device number */
|
|
int prev_dev_lr_volume; /* last known left/right volume
|
|
* (in device format) */
|
|
float volume; /* volume, in [0, 1] */
|
|
float balance; /* balance, in [-1, 1] */
|
|
bool can_record; /* capable of recording? */
|
|
bool is_recording; /* is it recording? */
|
|
bool is_stereo; /* capable of stereo? */
|
|
bool is_muted; /* is it muted? */
|
|
} MixerChannel;
|
|
|
|
static const char *channel_names[] = { CHANNEL_NAMES };
|
|
static const char *short_names[] = SOUND_DEVICE_LABELS;
|
|
|
|
static int mixer_fd;
|
|
|
|
static MixerChannel mixer[SOUND_MIXER_NRDEVICES];
|
|
static int n_channels = 0;
|
|
static int cur_channel = 0;
|
|
|
|
static int prev_modify_counter = -1;
|
|
|
|
static bool get_mixer_state(void)
|
|
{
|
|
struct mixer_info m_info;
|
|
int dev_lr_volume, dev_left_volume, dev_right_volume;
|
|
float left, right;
|
|
int srcmask;
|
|
int ch;
|
|
|
|
/* to really keep track of updates */
|
|
static MixerChannel oldmixer[SOUND_MIXER_NRDEVICES];
|
|
|
|
ioctl(mixer_fd, SOUND_MIXER_INFO, &m_info);
|
|
|
|
if (m_info.modify_counter == prev_modify_counter)
|
|
/*
|
|
* Mixer state has not changed
|
|
*/
|
|
return false;
|
|
|
|
/* Mixer state was changed by another program, so we need
|
|
* to update. As OSS cannot tell us specifically which
|
|
* channels changed, we read all of them in.
|
|
*
|
|
* prev_modify_counter was initialized to -1, so this part
|
|
* is guaranteed to run the first time this routine is
|
|
* called.
|
|
*/
|
|
|
|
if (ioctl(mixer_fd, SOUND_MIXER_READ_RECSRC, &srcmask) == -1) {
|
|
fprintf(stderr, "mixer read failed\n");
|
|
perror(NULL);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
for (ch = 0; ch < n_channels; ch++) {
|
|
if (ioctl(mixer_fd, MIXER_READ(mixer[ch].dev), &dev_lr_volume) ==
|
|
-1) {
|
|
fprintf(stderr, "mixer read failed\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (dev_lr_volume != mixer[ch].prev_dev_lr_volume) {
|
|
dev_left_volume = dev_lr_volume & 0xFF;
|
|
dev_right_volume = dev_lr_volume >> 8;
|
|
|
|
if ((dev_left_volume > 0) || (dev_right_volume > 0))
|
|
mixer[ch].is_muted = false;
|
|
|
|
left = (float) dev_left_volume / 100.0;
|
|
right = (float) dev_right_volume / 100.0;
|
|
|
|
if (!mixer[ch].is_muted) {
|
|
if (mixer[ch].is_stereo)
|
|
lr_to_vb(left,
|
|
right, &mixer[ch].volume, &mixer[ch].balance);
|
|
else {
|
|
mixer[ch].volume = left;
|
|
mixer[ch].balance = 0.0;
|
|
}
|
|
|
|
mixer[ch].prev_dev_lr_volume = dev_lr_volume;
|
|
}
|
|
}
|
|
mixer[ch].is_recording = ((1 << mixer[ch].dev) & srcmask) != 0;
|
|
}
|
|
prev_modify_counter = m_info.modify_counter;
|
|
/* check if this was due to OSS stupidity or if we really changed */
|
|
if (!memcmp(&mixer, &oldmixer, sizeof(mixer))) {
|
|
memcpy(&oldmixer, &mixer, sizeof(mixer));
|
|
return false;
|
|
}
|
|
memcpy(&oldmixer, &mixer, sizeof(mixer));
|
|
return true;
|
|
}
|
|
|
|
static void set_mixer_state(void)
|
|
{
|
|
float left, right;
|
|
int dev_left_volume, dev_right_volume, dev_lr_volume;
|
|
|
|
if (mixer[cur_channel].is_muted) {
|
|
left = 0.0;
|
|
right = 0.0;
|
|
} else
|
|
vb_to_lr(mixer[cur_channel].volume,
|
|
mixer[cur_channel].balance, &left, &right);
|
|
|
|
dev_left_volume = (int) (100.0 * left);
|
|
dev_right_volume = (int) (100.0 * right);
|
|
dev_lr_volume = (dev_right_volume << 8) | dev_left_volume;
|
|
ioctl(mixer_fd, MIXER_WRITE(mixer[cur_channel].dev), &dev_lr_volume);
|
|
}
|
|
|
|
static void get_record_state(void)
|
|
{
|
|
int srcmask;
|
|
int ch;
|
|
|
|
if (ioctl(mixer_fd, SOUND_MIXER_READ_RECSRC, &srcmask) == -1) {
|
|
fprintf(stderr, "mixer read failed\n");
|
|
perror(NULL);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
for (ch = 0; ch < n_channels; ch++) {
|
|
mixer[ch].is_recording = ((1 << mixer[ch].dev) & srcmask) != 0;
|
|
}
|
|
}
|
|
|
|
static void set_record_state(void)
|
|
{
|
|
int srcmask;
|
|
|
|
if (ioctl(mixer_fd, SOUND_MIXER_READ_RECSRC, &srcmask) == -1) {
|
|
fputs("error: recording source mask ioctl failed\n", stderr);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (((1 << mixer[cur_channel].dev) & srcmask) == 0)
|
|
srcmask |= (1 << mixer[cur_channel].dev);
|
|
else
|
|
srcmask &= ~(1 << mixer[cur_channel].dev);
|
|
|
|
if (ioctl(mixer_fd, SOUND_MIXER_WRITE_RECSRC, &srcmask) == -1) {
|
|
fputs("error: recording source mask ioctl failed\n", stderr);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
void mixer_init(const char *mixer_device, bool verbose, const char * exclude[])
|
|
{
|
|
int devmask, srcmask, recmask, stmask;
|
|
struct mixer_info m_info;
|
|
int count;
|
|
int mask;
|
|
|
|
mixer_fd = open(mixer_device, O_RDWR);
|
|
|
|
if (mixer_fd == -1) {
|
|
fprintf(stderr, "error: cannot open mixer device %s\n",
|
|
mixer_device);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
|
|
fputs("error: device mask ioctl failed\n", stderr);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (ioctl(mixer_fd, SOUND_MIXER_READ_RECSRC, &srcmask) == -1) {
|
|
fputs("error: recording source mask ioctl failed\n", stderr);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (ioctl(mixer_fd, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
|
|
fputs("error: recording mask ioctl failed\n", stderr);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (ioctl(mixer_fd, SOUND_MIXER_READ_STEREODEVS, &stmask) == -1) {
|
|
fputs("error: stereo mask ioctl failed\n", stderr);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (ioctl(mixer_fd, SOUND_MIXER_INFO, &m_info) == -1) {
|
|
fputs("error: could not read mixer info\n", stderr);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (verbose) {
|
|
printf("Sound card: %s (%s)\n", m_info.name, m_info.id);
|
|
puts("Supported channels:");
|
|
}
|
|
for (count = 0; count < SOUND_MIXER_NRDEVICES; count++) {
|
|
mask = 1 << count;
|
|
if (!(mask & devmask))
|
|
continue;
|
|
|
|
if (!is_exclude(short_names[count], exclude)) {
|
|
mixer[n_channels].name = channel_names[count];
|
|
mixer[n_channels].sname = short_names[count];
|
|
mixer[n_channels].dev = count;
|
|
mixer[n_channels].prev_dev_lr_volume = -1;
|
|
mixer[n_channels].can_record = (mask & recmask) != 0;
|
|
mixer[n_channels].is_recording = (mask & srcmask) != 0;
|
|
mixer[n_channels].is_stereo = (mask & stmask) != 0;
|
|
mixer[n_channels].is_muted = false;
|
|
++n_channels;
|
|
if (verbose)
|
|
printf(" %d: %s \t(%s)\n", n_channels, channel_names[count], short_names[count]);
|
|
} else if (verbose)
|
|
printf(" x: %s \t(%s) - disabled\n", channel_names[count], short_names[count]);
|
|
}
|
|
get_mixer_state();
|
|
}
|
|
|
|
bool mixer_is_changed(void)
|
|
{
|
|
return get_mixer_state();
|
|
}
|
|
|
|
int mixer_get_channel_count(void)
|
|
{
|
|
return n_channels;
|
|
}
|
|
|
|
int mixer_get_channel(void)
|
|
{
|
|
return cur_channel;
|
|
}
|
|
|
|
const char *mixer_get_channel_name(void)
|
|
{
|
|
return mixer[cur_channel].name;
|
|
}
|
|
|
|
const char *mixer_get_short_name(void)
|
|
{
|
|
return mixer[cur_channel].sname;
|
|
}
|
|
|
|
void mixer_set_channel(int channel)
|
|
{
|
|
assert((channel >= 0) && (channel < n_channels));
|
|
|
|
cur_channel = channel;
|
|
get_record_state();
|
|
}
|
|
|
|
void mixer_set_channel_rel(int delta_channel)
|
|
{
|
|
cur_channel = (cur_channel + delta_channel) % n_channels;
|
|
if (cur_channel < 0)
|
|
cur_channel += n_channels;
|
|
get_record_state();
|
|
}
|
|
|
|
float mixer_get_volume(void)
|
|
{
|
|
get_mixer_state();
|
|
return mixer[cur_channel].volume;
|
|
}
|
|
|
|
void mixer_set_volume(float volume)
|
|
{
|
|
assert((volume >= 0.0) && (volume <= 1.0));
|
|
|
|
mixer[cur_channel].volume = volume;
|
|
set_mixer_state();
|
|
}
|
|
|
|
void mixer_set_volume_rel(float delta_volume)
|
|
{
|
|
mixer[cur_channel].volume += delta_volume;
|
|
mixer[cur_channel].volume = CLAMP(mixer[cur_channel].volume, 0.0, 1.0);
|
|
set_mixer_state();
|
|
}
|
|
|
|
float mixer_get_balance(void)
|
|
{
|
|
get_mixer_state();
|
|
return mixer[cur_channel].balance;
|
|
}
|
|
|
|
void mixer_set_balance(float balance)
|
|
{
|
|
assert((balance >= -1.0) && (balance <= 1.0));
|
|
|
|
if (mixer[cur_channel].is_stereo) {
|
|
mixer[cur_channel].balance = balance;
|
|
set_mixer_state();
|
|
}
|
|
}
|
|
|
|
void mixer_set_balance_rel(float delta_balance)
|
|
{
|
|
if (mixer[cur_channel].is_stereo) {
|
|
mixer[cur_channel].balance += delta_balance;
|
|
mixer[cur_channel].balance =
|
|
CLAMP(mixer[cur_channel].balance, -1.0, 1.0);
|
|
set_mixer_state();
|
|
}
|
|
}
|
|
|
|
void mixer_toggle_mute(void)
|
|
{
|
|
mixer[cur_channel].is_muted = !mixer[cur_channel].is_muted;
|
|
|
|
set_mixer_state();
|
|
}
|
|
|
|
void mixer_toggle_rec(void)
|
|
{
|
|
if (mixer[cur_channel].can_record) {
|
|
mixer[cur_channel].is_recording = !mixer[cur_channel].is_recording;
|
|
set_record_state();
|
|
get_record_state();
|
|
}
|
|
}
|
|
|
|
bool mixer_is_muted(void)
|
|
{
|
|
return mixer[cur_channel].is_muted;
|
|
}
|
|
|
|
bool mixer_is_stereo(void)
|
|
{
|
|
return mixer[cur_channel].is_stereo;
|
|
}
|
|
|
|
bool mixer_is_rec(void)
|
|
{
|
|
return mixer[cur_channel].is_recording;
|
|
}
|
|
|
|
bool mixer_can_rec(void)
|
|
{
|
|
return mixer[cur_channel].can_record;
|
|
}
|
|
|
|
bool is_exclude(const char *short_name, const char *exclude[])
|
|
{
|
|
int count;
|
|
int len;
|
|
|
|
for (count = 0; count < SOUND_MIXER_NRDEVICES; count++) {
|
|
if (exclude[count] == NULL)
|
|
break;
|
|
|
|
/*
|
|
* Short names may be padded with spaces, because apparently there is a minimum
|
|
* length requirement of 6 characters for the name, and we do not want to
|
|
* include this padding in the match
|
|
*/
|
|
len = strlen(short_name);
|
|
while (len > 0) {
|
|
if (short_name[len - 1] == ' ')
|
|
len--;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (strncmp(short_name, exclude[count], len) != 0)
|
|
continue;
|
|
|
|
if (exclude[count][len] != '\0')
|
|
continue;
|
|
|
|
/* Check the remaining in short name is only space */
|
|
while (short_name[len] == ' ')
|
|
len++;
|
|
|
|
if (short_name[len] == '\0')
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|