aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/sound/pcm/mixer.c
diff options
context:
space:
mode:
authorAriff Abdullah <ariff@FreeBSD.org>2009-06-07 19:12:08 +0000
committerAriff Abdullah <ariff@FreeBSD.org>2009-06-07 19:12:08 +0000
commit90da2b2859b259fca1ab223931f549a72e21a3a5 (patch)
tree906f402638735c3f32e226f6868f207db569d6a9 /sys/dev/sound/pcm/mixer.c
parent0a276edef96edc86d1af97b39f76ad168599ceb4 (diff)
downloadsrc-90da2b2859b259fca1ab223931f549a72e21a3a5.tar.gz
src-90da2b2859b259fca1ab223931f549a72e21a3a5.zip
Sound Mega-commit. Expect further cleanup until code freeze.
For a slightly thorough explaination, please refer to [1] http://people.freebsd.org/~ariff/SOUND_4.TXT.html . Summary of changes includes: 1 Volume Per-Channel (vpc). Provides private / standalone volume control unique per-stream pcm channel without touching master volume / pcm. Applications can directly use SNDCTL_DSP_[GET|SET][PLAY|REC]VOL, or for backwards compatibility, SOUND_MIXER_PCM through the opened dsp device instead of /dev/mixer. Special "bypass" mode is enabled through /dev/mixer which will automatically detect if the adjustment is made through /dev/mixer and forward its request to this private volume controller. Changes to this volume object will not interfere with other channels. Requirements: - SNDCTL_DSP_[GET|SET][PLAY|REC]_VOL are newer ioctls (OSSv4) which require specific application modifications (preferred). - No modifications required for using bypass mode, so applications like mplayer or xmms should work out of the box. Kernel hints: - hint.pcm.%d.vpc (0 = disable vpc). Kernel sysctls: - hw.snd.vpc_mixer_bypass (default: 1). Enable or disable /dev/mixer bypass mode. - hw.snd.vpc_autoreset (default: 1). By default, closing/opening /dev/dsp will reset the volume back to 0 db gain/attenuation. Setting this to 0 will preserve its settings across device closing/opening. - hw.snd.vpc_reset (default: 0). Panic/reset button to reset all volume settings back to 0 db. - hw.snd.vpc_0db (default: 45). 0 db relative to linear mixer value. 2 High quality fixed-point Bandlimited SINC sampling rate converter, based on Julius O'Smith's Digital Audio Resampling - http://ccrma.stanford.edu/~jos/resample/. It includes a filter design script written in awk (the clumsiest joke I've ever written) - 100% 32bit fixed-point, 64bit accumulator. - Possibly among the fastest (if not fastest) of its kind. - Resampling quality is tunable, either runtime or during kernel compilation (FEEDER_RATE_PRESETS). - Quality can be further customized during kernel compilation by defining FEEDER_RATE_PRESETS in /etc/make.conf. Kernel sysctls: - hw.snd.feeder_rate_quality. 0 - Zero-order Hold (ZOH). Fastest, bad quality. 1 - Linear Interpolation (LINEAR). Slightly slower than ZOH, better quality but still does not eliminate aliasing. 2 - (and above) - Sinc Interpolation(SINC). Best quality. SINC quality always start from 2 and above. Rough quality comparisons: - http://people.freebsd.org/~ariff/z_comparison/ 3 Bit-perfect mode. Bypasses all feeder/dsp effects. Pure sound will be directly fed into the hardware. 4 Parametric (compile time) Software Equalizer (Bass/Treble mixer). Can be customized by defining FEEDER_EQ_PRESETS in /etc/make.conf. 5 Transparent/Adaptive Virtual Channel. Now you don't have to disable vchans in order to make digital format pass through. It also makes vchans more dynamic by choosing a better format/rate among all the concurrent streams, which means that dev.pcm.X.play.vchanformat/rate becomes sort of optional. 6 Exclusive Stream, with special open() mode O_EXCL. This will "mute" other concurrent vchan streams and only allow a single channel with O_EXCL set to keep producing sound. Other Changes: * most feeder_* stuffs are compilable in userland. Let's not speculate whether we should go all out for it (save that for FreeBSD 16.0-RELEASE). * kobj signature fixups, thanks to Andriy Gapon <avg@freebsd.org> * pull out channel mixing logic out of vchan.c and create its own feeder_mixer for world justice. * various refactoring here and there, for good or bad. * activation of few more OSSv4 ioctls() (see [1] above). * opt_snd.h for possible compile time configuration: (mostly for debugging purposes, don't try these at home) SND_DEBUG SND_DIAGNOSTIC SND_FEEDER_MULTIFORMAT SND_FEEDER_FULL_MULTIFORMAT SND_FEEDER_RATE_HP SND_PCM_64 SND_OLDSTEREO Manual page updates are on the way. Tested by: joel, Olivier SMEDTS <olivier at gid0 d org>, too many unsung / unnamed heroes.
Notes
Notes: svn path=/head/; revision=193640
Diffstat (limited to 'sys/dev/sound/pcm/mixer.c')
-rw-r--r--sys/dev/sound/pcm/mixer.c273
1 files changed, 230 insertions, 43 deletions
diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c
index d25d74549584..2b8d6ce771c4 100644
--- a/sys/dev/sound/pcm/mixer.c
+++ b/sys/dev/sound/pcm/mixer.c
@@ -1,5 +1,7 @@
/*-
- * Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
+ * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
+ * Portions Copyright (c) Ryan Beasley <ryan.beasley@gmail.com> - GSoC 2006
+ * Copyright (c) 1999 Cameron Grant <cg@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -24,14 +26,25 @@
* SUCH DAMAGE.
*/
+#ifdef HAVE_KERNEL_OPTION_HEADERS
+#include "opt_snd.h"
+#endif
+
#include <dev/sound/pcm/sound.h>
+#include "feeder_if.h"
#include "mixer_if.h"
SND_DECLARE_FILE("$FreeBSD$");
MALLOC_DEFINE(M_MIXER, "mixer", "mixer");
+static int mixer_bypass = 1;
+TUNABLE_INT("hw.snd.vpc_mixer_bypass", &mixer_bypass);
+SYSCTL_INT(_hw_snd, OID_AUTO, vpc_mixer_bypass, CTLFLAG_RW,
+ &mixer_bypass, 0,
+ "control channel pcm/rec volume, bypassing real mixer device");
+
#define MIXER_NAMELEN 16
struct snd_mixer {
KOBJ_FIELDS;
@@ -98,9 +111,7 @@ static struct cdevsw mixer_cdevsw = {
*/
int mixer_count = 0;
-#ifdef USING_DEVFS
static eventhandler_tag mixer_ehtag = NULL;
-#endif
static struct cdev *
mixer_get_devt(device_t dev)
@@ -112,7 +123,6 @@ mixer_get_devt(device_t dev)
return snddev->mixer_dev;
}
-#ifdef SND_DYNSYSCTL
static int
mixer_lookup(char *devname)
{
@@ -124,21 +134,20 @@ mixer_lookup(char *devname)
return i;
return -1;
}
-#endif
#define MIXER_SET_UNLOCK(x, y) do { \
if ((y) != 0) \
snd_mtxunlock((x)->lock); \
-} while(0)
+} while (0)
#define MIXER_SET_LOCK(x, y) do { \
if ((y) != 0) \
snd_mtxlock((x)->lock); \
-} while(0)
+} while (0)
static int
mixer_set_softpcmvol(struct snd_mixer *m, struct snddev_info *d,
- unsigned left, unsigned right)
+ u_int left, u_int right)
{
struct pcm_channel *c;
int dropmtx, acquiremtx;
@@ -166,22 +175,13 @@ mixer_set_softpcmvol(struct snd_mixer *m, struct snddev_info *d,
MIXER_SET_UNLOCK(m, dropmtx);
MIXER_SET_LOCK(d, acquiremtx);
- if (CHN_EMPTY(d, channels.pcm.busy)) {
- CHN_FOREACH(c, d, channels.pcm) {
- CHN_LOCK(c);
- if (c->direction == PCMDIR_PLAY &&
- (c->feederflags & (1 << FEEDER_VOLUME)))
- chn_setvolume(c, left, right);
- CHN_UNLOCK(c);
- }
- } else {
- CHN_FOREACH(c, d, channels.pcm.busy) {
- CHN_LOCK(c);
- if (c->direction == PCMDIR_PLAY &&
- (c->feederflags & (1 << FEEDER_VOLUME)))
- chn_setvolume(c, left, right);
- CHN_UNLOCK(c);
- }
+ CHN_FOREACH(c, d, channels.pcm.busy) {
+ CHN_LOCK(c);
+ if (c->direction == PCMDIR_PLAY &&
+ (c->feederflags & (1 << FEEDER_VOLUME)))
+ chn_setvolume_multi(c, SND_VOL_C_MASTER, left, right,
+ (left + right) >> 1);
+ CHN_UNLOCK(c);
}
MIXER_SET_UNLOCK(d, acquiremtx);
@@ -191,10 +191,62 @@ mixer_set_softpcmvol(struct snd_mixer *m, struct snddev_info *d,
}
static int
-mixer_set(struct snd_mixer *m, unsigned dev, unsigned lev)
+mixer_set_eq(struct snd_mixer *m, struct snddev_info *d,
+ u_int dev, u_int level)
+{
+ struct pcm_channel *c;
+ struct pcm_feeder *f;
+ int tone, dropmtx, acquiremtx;
+
+ if (dev == SOUND_MIXER_TREBLE)
+ tone = FEEDEQ_TREBLE;
+ else if (dev == SOUND_MIXER_BASS)
+ tone = FEEDEQ_BASS;
+ else
+ return (EINVAL);
+
+ if (!PCM_REGISTERED(d))
+ return (EINVAL);
+
+ if (mtx_owned(m->lock))
+ dropmtx = 1;
+ else
+ dropmtx = 0;
+
+ if (!(d->flags & SD_F_MPSAFE) || mtx_owned(d->lock) != 0)
+ acquiremtx = 0;
+ else
+ acquiremtx = 1;
+
+ /*
+ * Be careful here. If we're coming from cdev ioctl, it is OK to
+ * not doing locking AT ALL (except on individual channel) since
+ * we've been heavily guarded by pcm cv, or if we're still
+ * under Giant influence. Since we also have mix_* calls, we cannot
+ * assume such protection and just do the lock as usuall.
+ */
+ MIXER_SET_UNLOCK(m, dropmtx);
+ MIXER_SET_LOCK(d, acquiremtx);
+
+ CHN_FOREACH(c, d, channels.pcm.busy) {
+ CHN_LOCK(c);
+ f = chn_findfeeder(c, FEEDER_EQ);
+ if (f != NULL)
+ (void)FEEDER_SET(f, tone, level);
+ CHN_UNLOCK(c);
+ }
+
+ MIXER_SET_UNLOCK(d, acquiremtx);
+ MIXER_SET_LOCK(m, dropmtx);
+
+ return (0);
+}
+
+static int
+mixer_set(struct snd_mixer *m, u_int dev, u_int lev)
{
struct snddev_info *d;
- unsigned l, r, tl, tr;
+ u_int l, r, tl, tr;
u_int32_t parent = SOUND_MIXER_NONE, child = 0;
u_int32_t realdev;
int i, dropmtx;
@@ -243,7 +295,8 @@ mixer_set(struct snd_mixer *m, unsigned dev, unsigned lev)
realdev = m->realdev[i];
tl = (l * (m->level[i] & 0x00ff)) / 100;
tr = (r * ((m->level[i] & 0xff00) >> 8)) / 100;
- if (i == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL))
+ if (i == SOUND_MIXER_PCM &&
+ (d->flags & SD_F_SOFTPCMVOL))
(void)mixer_set_softpcmvol(m, d, tl, tr);
else if (realdev != SOUND_MIXER_NONE)
MIXER_SET(m, realdev, tl, tr);
@@ -257,6 +310,9 @@ mixer_set(struct snd_mixer *m, unsigned dev, unsigned lev)
} else {
if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL))
(void)mixer_set_softpcmvol(m, d, l, r);
+ else if ((dev == SOUND_MIXER_TREBLE ||
+ dev == SOUND_MIXER_BASS) && (d->flags & SD_F_EQ))
+ (void)mixer_set_eq(m, d, dev, (l + r) >> 1);
else if (realdev != SOUND_MIXER_NONE &&
MIXER_SET(m, realdev, l, r) < 0) {
MIXER_SET_LOCK(m, dropmtx);
@@ -264,10 +320,10 @@ mixer_set(struct snd_mixer *m, unsigned dev, unsigned lev)
}
}
- m->level[dev] = l | (r << 8);
-
MIXER_SET_LOCK(m, dropmtx);
+ m->level[dev] = l | (r << 8);
+
return 0;
}
@@ -284,6 +340,7 @@ static int
mixer_setrecsrc(struct snd_mixer *mixer, u_int32_t src)
{
struct snddev_info *d;
+ u_int32_t recsrc;
int dropmtx;
d = device_get_softc(mixer->dev);
@@ -298,8 +355,11 @@ mixer_setrecsrc(struct snd_mixer *mixer, u_int32_t src)
src = SOUND_MASK_MIC;
/* It is safe to drop this mutex due to Giant. */
MIXER_SET_UNLOCK(mixer, dropmtx);
- mixer->recsrc = MIXER_SETRECSRC(mixer, src);
+ recsrc = MIXER_SETRECSRC(mixer, src);
MIXER_SET_LOCK(mixer, dropmtx);
+
+ mixer->recsrc = recsrc;
+
return 0;
}
@@ -398,6 +458,8 @@ mix_setdevs(struct snd_mixer *m, u_int32_t v)
d = device_get_softc(m->dev);
if (d != NULL && (d->flags & SD_F_SOFTPCMVOL))
v |= SOUND_MASK_PCM;
+ if (d != NULL && (d->flags & SD_F_EQ))
+ v |= SOUND_MASK_TREBLE | SOUND_MASK_BASS;
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (m->parent[i] < SOUND_MIXER_NRDEVICES)
v |= 1 << m->parent[i];
@@ -623,6 +685,20 @@ mixer_init(device_t dev, kobj_class_t cls, void *devinfo)
struct cdev *pdev;
int i, unit, devunit, val;
+ snddev = device_get_softc(dev);
+ if (snddev == NULL)
+ return (-1);
+
+ if (resource_int_value(device_get_name(dev),
+ device_get_unit(dev), "eq", &val) == 0 && val != 0) {
+ snddev->flags |= SD_F_EQ;
+ if ((val & SD_F_EQ_MASK) == val)
+ snddev->flags |= val;
+ else
+ snddev->flags |= SD_F_EQ_DEFAULT;
+ snddev->eqpreamp = 0;
+ }
+
m = mixer_obj_create(dev, cls, devinfo, MIXER_TYPE_PRIMARY, NULL);
if (m == NULL)
return (-1);
@@ -644,10 +720,9 @@ mixer_init(device_t dev, kobj_class_t cls, void *devinfo)
unit = device_get_unit(dev);
devunit = snd_mkunit(unit, SND_DEV_CTL, 0);
- pdev = make_dev(&mixer_cdevsw, devunit,
+ pdev = make_dev(&mixer_cdevsw, PCMMINOR(devunit),
UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit);
pdev->si_drv1 = m;
- snddev = device_get_softc(dev);
snddev->mixer_dev = pdev;
++mixer_count;
@@ -674,6 +749,8 @@ mixer_init(device_t dev, kobj_class_t cls, void *devinfo)
}
if (snddev->flags & SD_F_SOFTPCMVOL)
device_printf(dev, "Soft PCM mixer ENABLED\n");
+ if (snddev->flags & SD_F_EQ)
+ device_printf(dev, "EQ Treble/Bass ENABLED\n");
}
return (0);
@@ -760,7 +837,6 @@ mixer_reinit(device_t dev)
return 0;
}
-#ifdef SND_DYNSYSCTL
static int
sysctl_hw_snd_hwvol_mixer(SYSCTL_HANDLER_ARGS)
{
@@ -788,7 +864,6 @@ sysctl_hw_snd_hwvol_mixer(SYSCTL_HANDLER_ARGS)
snd_mtxunlock(m->lock);
return error;
}
-#endif
int
mixer_hwvol_init(device_t dev)
@@ -801,7 +876,6 @@ mixer_hwvol_init(device_t dev)
m->hwvol_mixer = SOUND_MIXER_VOLUME;
m->hwvol_step = 5;
-#ifdef SND_DYNSYSCTL
SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
OID_AUTO, "hwvol_step", CTLFLAG_RW, &m->hwvol_step, 0, "");
@@ -809,7 +883,6 @@ mixer_hwvol_init(device_t dev)
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
OID_AUTO, "hwvol_mixer", CTLTYPE_STRING | CTLFLAG_RW, m, 0,
sysctl_hw_snd_hwvol_mixer, "A", "");
-#endif
return 0;
}
@@ -986,6 +1059,114 @@ mixer_close(struct cdev *i_dev, int flags, int mode, struct thread *td)
}
static int
+mixer_ioctl_channel(struct cdev *dev, u_long cmd, caddr_t arg, int mode,
+ struct thread *td, int from)
+{
+ struct snddev_info *d;
+ struct snd_mixer *m;
+ struct pcm_channel *c, *rdch, *wrch;
+ pid_t pid;
+ int j, ret;
+
+ if (td == NULL || td->td_proc == NULL)
+ return (-1);
+
+ m = dev->si_drv1;
+ d = device_get_softc(m->dev);
+ j = cmd & 0xff;
+
+ switch (j) {
+ case SOUND_MIXER_PCM:
+ case SOUND_MIXER_RECLEV:
+ case SOUND_MIXER_DEVMASK:
+ case SOUND_MIXER_CAPS:
+ case SOUND_MIXER_STEREODEVS:
+ break;
+ default:
+ return (-1);
+ break;
+ }
+
+ pid = td->td_proc->p_pid;
+ rdch = NULL;
+ wrch = NULL;
+ c = NULL;
+ ret = -1;
+
+ /*
+ * This is unfair. Imagine single proc opening multiple
+ * instances of same direction. What we do right now
+ * is looking for the first matching proc/pid, and just
+ * that. Nothing more. Consider it done.
+ *
+ * The better approach of controlling specific channel
+ * pcm or rec volume is by doing mixer ioctl
+ * (SNDCTL_DSP_[SET|GET][PLAY|REC]VOL / SOUND_MIXER_[PCM|RECLEV]
+ * on its open fd, rather than cracky mixer bypassing here.
+ */
+ CHN_FOREACH(c, d, channels.pcm.opened) {
+ CHN_LOCK(c);
+ if (c->pid != pid ||
+ !(c->feederflags & (1 << FEEDER_VOLUME))) {
+ CHN_UNLOCK(c);
+ continue;
+ }
+ if (rdch == NULL && c->direction == PCMDIR_REC) {
+ rdch = c;
+ if (j == SOUND_MIXER_RECLEV)
+ goto mixer_ioctl_channel_proc;
+ } else if (wrch == NULL && c->direction == PCMDIR_PLAY) {
+ wrch = c;
+ if (j == SOUND_MIXER_PCM)
+ goto mixer_ioctl_channel_proc;
+ }
+ CHN_UNLOCK(c);
+ if (rdch != NULL && wrch != NULL)
+ break;
+ }
+
+ if (rdch == NULL && wrch == NULL)
+ return (-1);
+
+ if ((j == SOUND_MIXER_DEVMASK || j == SOUND_MIXER_CAPS ||
+ j == SOUND_MIXER_STEREODEVS) &&
+ (cmd & MIXER_READ(0)) == MIXER_READ(0)) {
+ snd_mtxlock(m->lock);
+ *(int *)arg = mix_getdevs(m);
+ snd_mtxunlock(m->lock);
+ if (rdch != NULL)
+ *(int *)arg |= SOUND_MASK_RECLEV;
+ if (wrch != NULL)
+ *(int *)arg |= SOUND_MASK_PCM;
+ ret = 0;
+ }
+
+ return (ret);
+
+mixer_ioctl_channel_proc:
+
+ KASSERT(c != NULL, ("%s(): NULL channel", __func__));
+ CHN_LOCKASSERT(c);
+
+ if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) {
+ int left, right, center;
+
+ left = *(int *)arg & 0x7f;
+ right = (*(int *)arg >> 8) & 0x7f;
+ center = (left + right) >> 1;
+ chn_setvolume_multi(c, SND_VOL_C_PCM, left, right, center);
+ } else if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) {
+ *(int *)arg = CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL);
+ *(int *)arg |=
+ CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR) << 8;
+ }
+
+ CHN_UNLOCK(c);
+
+ return (0);
+}
+
+static int
mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
struct thread *td)
{
@@ -1002,7 +1183,15 @@ mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
PCM_GIANT_ENTER(d);
PCM_ACQUIRE_QUICK(d);
- ret = mixer_ioctl_cmd(i_dev, cmd, arg, mode, td, MIXER_CMD_CDEV);
+ ret = -1;
+
+ if (mixer_bypass != 0 && (d->flags & SD_F_VPC))
+ ret = mixer_ioctl_channel(i_dev, cmd, arg, mode, td,
+ MIXER_CMD_CDEV);
+
+ if (ret == -1)
+ ret = mixer_ioctl_cmd(i_dev, cmd, arg, mode, td,
+ MIXER_CMD_CDEV);
PCM_RELEASE_QUICK(d);
PCM_GIANT_LEAVE(d);
@@ -1012,7 +1201,7 @@ mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
/*
* XXX Make sure you can guarantee concurrency safety before calling this
- * function, be it through Giant, PCM_CV_*, etc !
+ * function, be it through Giant, PCM_*, etc !
*/
int
mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
@@ -1112,7 +1301,6 @@ mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
return (ret);
}
-#ifdef USING_DEVFS
static void
mixer_clone(void *arg,
#if __FreeBSD_version >= 600034
@@ -1152,7 +1340,6 @@ mixer_sysuninit(void *p)
SYSINIT(mixer_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysinit, NULL);
SYSUNINIT(mixer_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysuninit, NULL);
-#endif
/**
* @brief Handler for SNDCTL_MIXERINFO
@@ -1204,8 +1391,8 @@ mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi)
/* XXX Need Giant magic entry */
/* See the note in function docblock. */
- mtx_assert(d->lock, MA_NOTOWNED);
- pcm_lock(d);
+ PCM_UNLOCKASSERT(d);
+ PCM_LOCK(d);
if (d->mixer_dev != NULL && d->mixer_dev->si_drv1 != NULL &&
((mi->dev == -1 && d->mixer_dev == i_dev) ||
@@ -1288,7 +1475,7 @@ mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi)
} else
++nmix;
- pcm_unlock(d);
+ PCM_UNLOCK(d);
if (m != NULL)
return (0);