diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h --- a/sys/dev/sound/pcm/channel.h +++ b/sys/dev/sound/pcm/channel.h @@ -166,7 +166,8 @@ struct pcmchan_matrix matrix; struct pcmchan_matrix matrix_scratch; - int volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX]; + int16_t volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX]; + int8_t muted[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX]; void *data1, *data2; }; @@ -271,6 +272,9 @@ int center); int chn_setvolume_matrix(struct pcm_channel *c, int vc, int vt, int val); int chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt); +int chn_setmute_multi(struct pcm_channel *c, int vc, int mute); +int chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute); +int chn_getmute_matrix(struct pcm_channel *c, int vc, int vt); void chn_vpc_reset(struct pcm_channel *c, int vc, int force); int chn_setparam(struct pcm_channel *c, uint32_t format, uint32_t speed); int chn_setspeed(struct pcm_channel *c, uint32_t speed); @@ -307,6 +311,8 @@ #define CHN_GETVOLUME(x, y, z) ((x)->volume[y][z]) #endif +#define CHN_GETMUTE(x, y, z) ((x)->muted[y][z]) + #ifdef OSSV4_EXPERIMENT int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak); #endif diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -1223,6 +1223,8 @@ c->volume[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_MASTER; c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB] = chn_vol_0db_pcm; + memset(c->muted, 0, sizeof(c->muted)); + chn_vpc_reset(c, SND_VOL_C_PCM, 1); ret = ENODEV; @@ -1394,6 +1396,75 @@ return (c->volume[vc][vt]); } +int +chn_setmute_multi(struct pcm_channel *c, int vc, int mute) +{ + int i, ret; + + ret = 0; + + for (i = 0; i < SND_CHN_T_MAX; i++) { + if ((1 << i) & SND_CHN_LEFT_MASK) + ret |= chn_setmute_matrix(c, vc, i, mute); + else if ((1 << i) & SND_CHN_RIGHT_MASK) + ret |= chn_setmute_matrix(c, vc, i, mute) << 8; + else + ret |= chn_setmute_matrix(c, vc, i, mute) << 16; + } + return (ret); +} + +int +chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute) +{ + int i; + + KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && + (vc == SND_VOL_C_MASTER || (vc & 1)) && + (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), + ("%s(): invalid mute matrix c=%p vc=%d vt=%d mute=%d", + __func__, c, vc, vt, mute)); + + CHN_LOCKASSERT(c); + + mute = (mute != 0); + + c->muted[vc][vt] = mute; + + /* + * Do relative calculation here and store it into class + 1 + * to ease the job of feeder_volume. + */ + if (vc == SND_VOL_C_MASTER) { + for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END; + vc += SND_VOL_C_STEP) + c->muted[SND_VOL_C_VAL(vc)][vt] = mute; + } else if (vc & 1) { + if (vt == SND_CHN_T_VOL_0DB) { + for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; + i += SND_CHN_T_STEP) { + c->muted[SND_VOL_C_VAL(vc)][i] = mute; + } + } else { + c->muted[SND_VOL_C_VAL(vc)][vt] = mute; + } + } + return (mute); +} + +int +chn_getmute_matrix(struct pcm_channel *c, int vc, int vt) +{ + KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && + (vt == SND_CHN_T_VOL_0DB || + (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), + ("%s(): invalid mute matrix c=%p vc=%d vt=%d", + __func__, c, vc, vt)); + CHN_LOCKASSERT(c); + + return (c->muted[vc][vt]); +} + struct pcmchan_matrix * chn_getmatrix(struct pcm_channel *c) { diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -965,6 +965,7 @@ struct snddev_info *d; struct pcm_channel *rdch, *wrch; int j, devtype, ret; + int left, right, center, mute; d = dsp_get_info(dev); if (!PCM_REGISTERED(d) || !(dsp_get_flags(dev) & SD_F_VPC)) @@ -1003,67 +1004,95 @@ } /* Final validation */ - if (volch != NULL) { - CHN_LOCK(volch); - if (!(volch->feederflags & (1 << FEEDER_VOLUME))) { - CHN_UNLOCK(volch); - return (-1); - } - if (volch->direction == PCMDIR_PLAY) - wrch = volch; - else - rdch = volch; - } - - ret = EINVAL; + if (volch == NULL) + return (EINVAL); - if (volch != NULL && - ((j == SOUND_MIXER_PCM && volch->direction == PCMDIR_PLAY) || - (j == SOUND_MIXER_RECLEV && volch->direction == PCMDIR_REC))) { - if ((cmd & ~0xff) == MIXER_WRITE(0)) { - int left, right, center; + CHN_LOCK(volch); + if (!(volch->feederflags & (1 << FEEDER_VOLUME))) { + CHN_UNLOCK(volch); + return (EINVAL); + } + switch (cmd & ~0xff) { + case MIXER_WRITE(0): + switch (j) { + case SOUND_MIXER_MUTE: + if (volch->direction == PCMDIR_REC) { + chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_RECLEV) != 0); + } else { + chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_PCM) != 0); + } + break; + case SOUND_MIXER_PCM: + if (volch->direction != PCMDIR_PLAY) + break; left = *(int *)arg & 0x7f; right = ((*(int *)arg) >> 8) & 0x7f; center = (left + right) >> 1; - chn_setvolume_multi(volch, SND_VOL_C_PCM, left, right, - center); - } else if ((cmd & ~0xff) == MIXER_READ(0)) { - *(int *)arg = CHN_GETVOLUME(volch, - SND_VOL_C_PCM, SND_CHN_T_FL); - *(int *)arg |= CHN_GETVOLUME(volch, - SND_VOL_C_PCM, SND_CHN_T_FR) << 8; + chn_setvolume_multi(volch, SND_VOL_C_PCM, + left, right, center); + break; + case SOUND_MIXER_RECLEV: + if (volch->direction != PCMDIR_REC) + break; + left = *(int *)arg & 0x7f; + right = ((*(int *)arg) >> 8) & 0x7f; + center = (left + right) >> 1; + chn_setvolume_multi(volch, SND_VOL_C_PCM, + left, right, center); + break; + default: + /* ignore all other mixer writes */ + break; } - ret = 0; - } else if (rdch != NULL || wrch != NULL) { + break; + + case MIXER_READ(0): switch (j) { + case SOUND_MIXER_MUTE: + mute = CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FL) || + CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FR); + if (volch->direction == PCMDIR_REC) { + *(int *)arg = mute << SOUND_MIXER_RECLEV; + } else { + *(int *)arg = mute << SOUND_MIXER_PCM; + } + break; + case SOUND_MIXER_PCM: + if (volch->direction != PCMDIR_PLAY) + break; + *(int *)arg = CHN_GETVOLUME(volch, + SND_VOL_C_PCM, SND_CHN_T_FL); + *(int *)arg |= CHN_GETVOLUME(volch, + SND_VOL_C_PCM, SND_CHN_T_FR) << 8; + break; + case SOUND_MIXER_RECLEV: + if (volch->direction != PCMDIR_REC) + break; + *(int *)arg = CHN_GETVOLUME(volch, + SND_VOL_C_PCM, SND_CHN_T_FL); + *(int *)arg |= CHN_GETVOLUME(volch, + SND_VOL_C_PCM, SND_CHN_T_FR) << 8; + break; case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: - if ((cmd & ~0xff) == MIXER_READ(0)) { - *(int *)arg = 0; - if (rdch != NULL) - *(int *)arg |= SOUND_MASK_RECLEV; - if (wrch != NULL) - *(int *)arg |= SOUND_MASK_PCM; - } - ret = 0; - break; - case SOUND_MIXER_RECMASK: - case SOUND_MIXER_RECSRC: - if ((cmd & ~0xff) == MIXER_READ(0)) - *(int *)arg = 0; - ret = 0; + if (volch->direction == PCMDIR_REC) + *(int *)arg = SOUND_MASK_RECLEV; + else + *(int *)arg = SOUND_MASK_PCM; break; default: + *(int *)arg = 0; break; } - } - - if (volch != NULL) - CHN_UNLOCK(volch); + break; - return (ret); + default: + break; + } + CHN_UNLOCK(volch); + return (0); } static int @@ -2294,8 +2323,7 @@ size_t len; len = strlen(namep); - - if (bcmp(name, namep, len) != 0) + if (strncmp(name, namep, len) != 0) return (ENODEV); name += len; diff --git a/sys/dev/sound/pcm/feeder_volume.c b/sys/dev/sound/pcm/feeder_volume.c --- a/sys/dev/sound/pcm/feeder_volume.c +++ b/sys/dev/sound/pcm/feeder_volume.c @@ -237,10 +237,13 @@ feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { + int temp_vol[SND_CHN_T_VOL_MAX]; struct feed_volume_info *info; uint32_t j, align; - int i, *vol, *matrix; + int i, *matrix; uint8_t *dst; + const int16_t *vol; + const int8_t *muted; /* * Fetch filter data operation. @@ -251,6 +254,7 @@ return (FEEDER_FEED(f->source, c, b, count, source)); vol = c->volume[SND_VOL_C_VAL(info->volume_class)]; + muted = c->muted[SND_VOL_C_VAL(info->volume_class)]; matrix = info->matrix; /* @@ -258,17 +262,22 @@ */ j = 0; i = info->channels; - do { - if (vol[matrix[--i]] != SND_VOL_FLAT) { + while (i--) { + if (vol[matrix[i]] != SND_VOL_FLAT || + muted[matrix[i]] != 0) { j = 1; break; } - } while (i != 0); + } /* Nope, just bypass entirely. */ if (j == 0) return (FEEDER_FEED(f->source, c, b, count, source)); + /* Check if any controls are muted. */ + for (j = 0; j != SND_CHN_T_VOL_MAX; j++) + temp_vol[j] = muted[j] ? 0 : vol[j]; + dst = b; align = info->bps * info->channels; @@ -281,7 +290,7 @@ if (j == 0) break; - info->apply(vol, matrix, info->channels, dst, j); + info->apply(temp_vol, matrix, info->channels, dst, j); j *= align; dst += j; diff --git a/sys/dev/sound/pcm/mixer.h b/sys/dev/sound/pcm/mixer.h --- a/sys/dev/sound/pcm/mixer.h +++ b/sys/dev/sound/pcm/mixer.h @@ -60,8 +60,10 @@ void mix_setdevs(struct snd_mixer *m, u_int32_t v); void mix_setrecdevs(struct snd_mixer *m, u_int32_t v); +void mix_setmutedevs(struct snd_mixer *m, u_int32_t v); u_int32_t mix_getdevs(struct snd_mixer *m); u_int32_t mix_getrecdevs(struct snd_mixer *m); +u_int32_t mix_getmutedevs(struct snd_mixer *m); void mix_setparentchild(struct snd_mixer *m, u_int32_t parent, u_int32_t childs); void mix_setrealdev(struct snd_mixer *m, u_int32_t dev, u_int32_t realdev); u_int32_t mix_getparent(struct snd_mixer *m, u_int32_t dev); diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c --- a/sys/dev/sound/pcm/mixer.c +++ b/sys/dev/sound/pcm/mixer.c @@ -51,16 +51,16 @@ KOBJ_FIELDS; void *devinfo; int busy; - int hwvol_muted; int hwvol_mixer; int hwvol_step; int type; device_t dev; - u_int32_t hwvol_mute_level; u_int32_t devs; + u_int32_t mutedevs; u_int32_t recdevs; u_int32_t recsrc; u_int16_t level[32]; + u_int16_t level_muted[32]; u_int8_t parent[32]; u_int32_t child[32]; u_int8_t realdev[32]; @@ -244,7 +244,7 @@ } static int -mixer_set(struct snd_mixer *m, u_int dev, u_int lev) +mixer_set(struct snd_mixer *m, u_int dev, u_int32_t muted, u_int lev) { struct snddev_info *d; u_int l, r, tl, tr; @@ -254,7 +254,7 @@ if (m == NULL || dev >= SOUND_MIXER_NRDEVICES || (0 == (m->devs & (1 << dev)))) - return -1; + return (-1); l = min((lev & 0x00ff), 100); r = min(((lev & 0xff00) >> 8), 100); @@ -262,7 +262,7 @@ d = device_get_softc(m->dev); if (d == NULL) - return -1; + return (-1); /* It is safe to drop this mutex due to Giant. */ if (!(d->flags & SD_F_MPSAFE) && mtx_owned(m->lock) != 0) @@ -270,6 +270,11 @@ else dropmtx = 0; + /* Allow the volume to be "changed" while muted. */ + if (muted & (1 << dev)) { + m->level_muted[dev] = l | (r << 8); + return (0); + } MIXER_SET_UNLOCK(m, dropmtx); /* TODO: recursive handling */ @@ -287,7 +292,7 @@ else if (realdev != SOUND_MIXER_NONE && MIXER_SET(m, realdev, tl, tr) < 0) { MIXER_SET_LOCK(m, dropmtx); - return -1; + return (-1); } } else if (child != 0) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { @@ -305,8 +310,8 @@ realdev = m->realdev[dev]; if (realdev != SOUND_MIXER_NONE && MIXER_SET(m, realdev, l, r) < 0) { - MIXER_SET_LOCK(m, dropmtx); - return -1; + MIXER_SET_LOCK(m, dropmtx); + return (-1); } } else { if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) @@ -317,7 +322,7 @@ else if (realdev != SOUND_MIXER_NONE && MIXER_SET(m, realdev, l, r) < 0) { MIXER_SET_LOCK(m, dropmtx); - return -1; + return (-1); } } @@ -326,16 +331,42 @@ m->level[dev] = l | (r << 8); m->modify_counter++; - return 0; + return (0); } static int mixer_get(struct snd_mixer *mixer, int dev) { - if ((dev < SOUND_MIXER_NRDEVICES) && (mixer->devs & (1 << dev))) - return mixer->level[dev]; - else - return -1; + if ((dev < SOUND_MIXER_NRDEVICES) && (mixer->devs & (1 << dev))) { + if (mixer->mutedevs & (1 << dev)) + return (mixer->level_muted[dev]); + else + return (mixer->level[dev]); + } else { + return (-1); + } +} + +void +mix_setmutedevs(struct snd_mixer *mixer, u_int32_t mutedevs) +{ + u_int32_t delta; + + /* Filter out invalid values. */ + mutedevs &= mixer->devs; + delta = (mixer->mutedevs ^ mutedevs) & mixer->devs; + mixer->mutedevs = mutedevs; + + for (int i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!(delta & (1 << i))) + continue; + if (mutedevs & (1 << i)) { + mixer->level_muted[i] = mixer->level[i]; + mixer_set(mixer, i, 0, 0); + } else { + mixer_set(mixer, i, 0, mixer->level_muted[i]); + } + } } static int @@ -598,6 +629,12 @@ return m->devs; } +u_int32_t +mix_getmutedevs(struct snd_mixer *m) +{ + return m->mutedevs; +} + u_int32_t mix_getrecdevs(struct snd_mixer *m) { @@ -721,7 +758,7 @@ } } - mixer_set(m, i, v | (v << 8)); + mixer_set(m, i, 0, v | (v << 8)); } mixer_setrecsrc(m, 0); /* Set default input. */ @@ -799,7 +836,7 @@ snd_mtxlock(m->lock); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) - mixer_set(m, i, 0); + mixer_set(m, i, 0, 0); mixer_setrecsrc(m, SOUND_MASK_MIC); @@ -836,8 +873,12 @@ return i; } - for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) - mixer_set(m, i, m->level[i]); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (m->mutedevs & (1 << i)) + mixer_set(m, i, 0, 0); + else + mixer_set(m, i, 0, m->level[i]); + } mixer_setrecsrc(m, m->recsrc); snd_mtxunlock(m->lock); @@ -863,10 +904,8 @@ if (dev == -1) { snd_mtxunlock(m->lock); return EINVAL; - } - else if (dev != m->hwvol_mixer) { + } else { m->hwvol_mixer = dev; - m->hwvol_muted = 0; } } snd_mtxunlock(m->lock); @@ -897,14 +936,7 @@ void mixer_hwvol_mute_locked(struct snd_mixer *m) { - if (m->hwvol_muted) { - m->hwvol_muted = 0; - mixer_set(m, m->hwvol_mixer, m->hwvol_mute_level); - } else { - m->hwvol_muted++; - m->hwvol_mute_level = mixer_get(m, m->hwvol_mixer); - mixer_set(m, m->hwvol_mixer, 0); - } + mix_setmutedevs(m, m->mutedevs ^ (1 << m->hwvol_mixer)); } void @@ -925,11 +957,8 @@ { int level, left, right; - if (m->hwvol_muted) { - m->hwvol_muted = 0; - level = m->hwvol_mute_level; - } else - level = mixer_get(m, m->hwvol_mixer); + level = mixer_get(m, m->hwvol_mixer); + if (level != -1) { left = level & 0xff; right = (level >> 8) & 0xff; @@ -943,7 +972,8 @@ right = 0; else if (right > 100) right = 100; - mixer_set(m, m->hwvol_mixer, left | right << 8); + + mixer_set(m, m->hwvol_mixer, m->mutedevs, left | right << 8); } } @@ -976,7 +1006,7 @@ KASSERT(m != NULL, ("NULL snd_mixer")); snd_mtxlock(m->lock); - ret = mixer_set(m, dev, left | (right << 8)); + ret = mixer_set(m, dev, m->mutedevs, left | (right << 8)); snd_mtxunlock(m->lock); return ((ret != 0) ? ENXIO : 0); @@ -1304,10 +1334,18 @@ goto done; } if ((cmd & ~0xff) == MIXER_WRITE(0)) { - if (j == SOUND_MIXER_RECSRC) + switch (j) { + case SOUND_MIXER_RECSRC: ret = mixer_setrecsrc(m, *arg_i); - else - ret = mixer_set(m, j, *arg_i); + break; + case SOUND_MIXER_MUTE: + mix_setmutedevs(m, *arg_i); + ret = 0; + break; + default: + ret = mixer_set(m, j, m->mutedevs, *arg_i); + break; + } snd_mtxunlock(m->lock); return ((ret == 0) ? 0 : ENXIO); } @@ -1318,6 +1356,9 @@ case SOUND_MIXER_STEREODEVS: v = mix_getdevs(m); break; + case SOUND_MIXER_MUTE: + v = mix_getmutedevs(m); + break; case SOUND_MIXER_RECMASK: v = mix_getrecdevs(m); break; @@ -1326,6 +1367,7 @@ break; default: v = mixer_get(m, j); + break; } *arg_i = v; snd_mtxunlock(m->lock); @@ -1554,5 +1596,5 @@ level = (left & 0xFF) | ((right & 0xFF) << 8); - return (mixer_set(m, dev, level)); + return (mixer_set(m, dev, m->mutedevs, level)); } diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -411,6 +411,10 @@ void sound_oss_sysinfo(oss_sysinfo *); int sound_oss_card_info(oss_card_info *); +#define PCM_MODE_MIXER 0x01 +#define PCM_MODE_PLAY 0x02 +#define PCM_MODE_REC 0x04 + #define PCM_LOCKOWNED(d) mtx_owned((d)->lock) #define PCM_LOCK(d) mtx_lock((d)->lock) #define PCM_UNLOCK(d) mtx_unlock((d)->lock) diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -1015,12 +1015,30 @@ "global clone garbage collector"); #endif +static u_int8_t +pcm_mode_init(struct snddev_info *d) +{ + u_int8_t mode = 0; + + if (d->playcount > 0) + mode |= PCM_MODE_PLAY; + if (d->reccount > 0) + mode |= PCM_MODE_REC; + if (d->mixer_dev != NULL) + mode |= PCM_MODE_MIXER; + + return (mode); +} + static void pcm_sysinit(device_t dev) { struct snddev_info *d = device_get_softc(dev); + u_int8_t mode; + + mode = pcm_mode_init(d); - /* XXX: an user should be able to set this with a control tool, the + /* XXX: a user should be able to set this with a control tool, the sysadmin then needs min+max sysctls for this */ SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), @@ -1030,6 +1048,11 @@ "bitperfect", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, d, sizeof(d), sysctl_dev_pcm_bitperfect, "I", "bit-perfect playback/recording (0=disable, 1=enable)"); + SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "mode", CTLFLAG_RD, NULL, mode, + "mode (1=mixer, 2=play, 4=rec. The values are OR'ed if more than one" + "mode is supported)"); #ifdef SND_DEBUG SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, @@ -1133,7 +1156,7 @@ sysctl_ctx_init(&d->rec_sysctl_ctx); d->rec_sysctl_tree = SYSCTL_ADD_NODE(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rec", - CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "record channels node"); + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "recording channels node"); if (numplay > 0 || numrec > 0) d->flags |= SD_F_AUTOVCHAN;