feeder_volume.c revision 330897
1130803Smarcel/*- 2130803Smarcel * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3130803Smarcel * 4130803Smarcel * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org> 5130803Smarcel * All rights reserved. 6130803Smarcel * 7130803Smarcel * Redistribution and use in source and binary forms, with or without 8130803Smarcel * modification, are permitted provided that the following conditions 9130803Smarcel * are met: 10130803Smarcel * 1. Redistributions of source code must retain the above copyright 11130803Smarcel * notice, this list of conditions and the following disclaimer. 12130803Smarcel * 2. Redistributions in binary form must reproduce the above copyright 13130803Smarcel * notice, this list of conditions and the following disclaimer in the 14130803Smarcel * documentation and/or other materials provided with the distribution. 15130803Smarcel * 16130803Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17130803Smarcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18130803Smarcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19130803Smarcel * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20130803Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21130803Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22130803Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23130803Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24130803Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25130803Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26130803Smarcel * SUCH DAMAGE. 27130803Smarcel */ 28130803Smarcel 29130803Smarcel/* feeder_volume, a long 'Lost Technology' rather than a new feature. */ 30130803Smarcel 31130803Smarcel#ifdef _KERNEL 32130803Smarcel#ifdef HAVE_KERNEL_OPTION_HEADERS 33130803Smarcel#include "opt_snd.h" 34130803Smarcel#endif 35130803Smarcel#include <dev/sound/pcm/sound.h> 36130803Smarcel#include <dev/sound/pcm/pcm.h> 37130803Smarcel#include "feeder_if.h" 38130803Smarcel 39130803Smarcel#define SND_USE_FXDIV 40130803Smarcel#include "snd_fxdiv_gen.h" 41130803Smarcel 42130803SmarcelSND_DECLARE_FILE("$FreeBSD: stable/11/sys/dev/sound/pcm/feeder_volume.c 330897 2018-03-14 03:19:51Z eadler $"); 43130803Smarcel#endif 44130803Smarcel 45130803Smarceltypedef void (*feed_volume_t)(int *, int *, uint32_t, uint8_t *, uint32_t); 46130803Smarcel 47130803Smarcel#define FEEDVOLUME_CALC8(s, v) (SND_VOL_CALC_SAMPLE((intpcm_t) \ 48130803Smarcel (s) << 8, v) >> 8) 49130803Smarcel#define FEEDVOLUME_CALC16(s, v) SND_VOL_CALC_SAMPLE((intpcm_t)(s), v) 50130803Smarcel#define FEEDVOLUME_CALC24(s, v) SND_VOL_CALC_SAMPLE((intpcm64_t)(s), v) 51130803Smarcel#define FEEDVOLUME_CALC32(s, v) SND_VOL_CALC_SAMPLE((intpcm64_t)(s), v) 52130803Smarcel 53130803Smarcel#define FEEDVOLUME_DECLARE(SIGN, BIT, ENDIAN) \ 54130803Smarcelstatic void \ 55130803Smarcelfeed_volume_##SIGN##BIT##ENDIAN(int *vol, int *matrix, \ 56130803Smarcel uint32_t channels, uint8_t *dst, uint32_t count) \ 57130803Smarcel{ \ 58130803Smarcel intpcm##BIT##_t v; \ 59130803Smarcel intpcm_t x; \ 60130803Smarcel uint32_t i; \ 61130803Smarcel \ 62130803Smarcel dst += count * PCM_##BIT##_BPS * channels; \ 63130803Smarcel do { \ 64130803Smarcel i = channels; \ 65130803Smarcel do { \ 66130803Smarcel dst -= PCM_##BIT##_BPS; \ 67130803Smarcel i--; \ 68130803Smarcel x = PCM_READ_##SIGN##BIT##_##ENDIAN(dst); \ 69130803Smarcel v = FEEDVOLUME_CALC##BIT(x, vol[matrix[i]]); \ 70130803Smarcel x = PCM_CLAMP_##SIGN##BIT(v); \ 71130803Smarcel _PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst, x); \ 72130803Smarcel } while (i != 0); \ 73130803Smarcel } while (--count != 0); \ 74130803Smarcel} 75130803Smarcel 76130803Smarcel#if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) 77130803SmarcelFEEDVOLUME_DECLARE(S, 16, LE) 78130803SmarcelFEEDVOLUME_DECLARE(S, 32, LE) 79130803Smarcel#endif 80130803Smarcel#if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) 81130803SmarcelFEEDVOLUME_DECLARE(S, 16, BE) 82130803SmarcelFEEDVOLUME_DECLARE(S, 32, BE) 83130803Smarcel#endif 84130803Smarcel#ifdef SND_FEEDER_MULTIFORMAT 85130803SmarcelFEEDVOLUME_DECLARE(S, 8, NE) 86130803SmarcelFEEDVOLUME_DECLARE(S, 24, LE) 87130803SmarcelFEEDVOLUME_DECLARE(S, 24, BE) 88130803SmarcelFEEDVOLUME_DECLARE(U, 8, NE) 89130803SmarcelFEEDVOLUME_DECLARE(U, 16, LE) 90130803SmarcelFEEDVOLUME_DECLARE(U, 24, LE) 91130803SmarcelFEEDVOLUME_DECLARE(U, 32, LE) 92130803SmarcelFEEDVOLUME_DECLARE(U, 16, BE) 93130803SmarcelFEEDVOLUME_DECLARE(U, 24, BE) 94130803SmarcelFEEDVOLUME_DECLARE(U, 32, BE) 95130803Smarcel#endif 96130803Smarcel 97130803Smarcelstruct feed_volume_info { 98130803Smarcel uint32_t bps, channels; 99130803Smarcel feed_volume_t apply; 100130803Smarcel int volume_class; 101130803Smarcel int state; 102130803Smarcel int matrix[SND_CHN_MAX]; 103130803Smarcel}; 104130803Smarcel 105130803Smarcel#define FEEDVOLUME_ENTRY(SIGN, BIT, ENDIAN) \ 106130803Smarcel { \ 107130803Smarcel AFMT_##SIGN##BIT##_##ENDIAN, \ 108130803Smarcel feed_volume_##SIGN##BIT##ENDIAN \ 109130803Smarcel } 110130803Smarcel 111130803Smarcelstatic const struct { 112130803Smarcel uint32_t format; 113130803Smarcel feed_volume_t apply; 114130803Smarcel} feed_volume_info_tab[] = { 115130803Smarcel#if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) 116130803Smarcel FEEDVOLUME_ENTRY(S, 16, LE), 117130803Smarcel FEEDVOLUME_ENTRY(S, 32, LE), 118130803Smarcel#endif 119130803Smarcel#if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) 120130803Smarcel FEEDVOLUME_ENTRY(S, 16, BE), 121130803Smarcel FEEDVOLUME_ENTRY(S, 32, BE), 122130803Smarcel#endif 123130803Smarcel#ifdef SND_FEEDER_MULTIFORMAT 124130803Smarcel FEEDVOLUME_ENTRY(S, 8, NE), 125130803Smarcel FEEDVOLUME_ENTRY(S, 24, LE), 126130803Smarcel FEEDVOLUME_ENTRY(S, 24, BE), 127130803Smarcel FEEDVOLUME_ENTRY(U, 8, NE), 128130803Smarcel FEEDVOLUME_ENTRY(U, 16, LE), 129130803Smarcel FEEDVOLUME_ENTRY(U, 24, LE), 130130803Smarcel FEEDVOLUME_ENTRY(U, 32, LE), 131130803Smarcel FEEDVOLUME_ENTRY(U, 16, BE), 132130803Smarcel FEEDVOLUME_ENTRY(U, 24, BE), 133130803Smarcel FEEDVOLUME_ENTRY(U, 32, BE) 134130803Smarcel#endif 135130803Smarcel}; 136130803Smarcel 137130803Smarcel#define FEEDVOLUME_TAB_SIZE ((int32_t) \ 138130803Smarcel (sizeof(feed_volume_info_tab) / \ 139130803Smarcel sizeof(feed_volume_info_tab[0]))) 140130803Smarcel 141130803Smarcelstatic int 142130803Smarcelfeed_volume_init(struct pcm_feeder *f) 143130803Smarcel{ 144130803Smarcel struct feed_volume_info *info; 145130803Smarcel struct pcmchan_matrix *m; 146130803Smarcel uint32_t i; 147130803Smarcel int ret; 148130803Smarcel 149130803Smarcel if (f->desc->in != f->desc->out || 150130803Smarcel AFMT_CHANNEL(f->desc->in) > SND_CHN_MAX) 151130803Smarcel return (EINVAL); 152130803Smarcel 153130803Smarcel for (i = 0; i < FEEDVOLUME_TAB_SIZE; i++) { 154130803Smarcel if (AFMT_ENCODING(f->desc->in) == 155130803Smarcel feed_volume_info_tab[i].format) { 156130803Smarcel info = malloc(sizeof(*info), M_DEVBUF, 157130803Smarcel M_NOWAIT | M_ZERO); 158130803Smarcel if (info == NULL) 159130803Smarcel return (ENOMEM); 160130803Smarcel 161130803Smarcel info->bps = AFMT_BPS(f->desc->in); 162130803Smarcel info->channels = AFMT_CHANNEL(f->desc->in); 163130803Smarcel info->apply = feed_volume_info_tab[i].apply; 164130803Smarcel info->volume_class = SND_VOL_C_PCM; 165130803Smarcel info->state = FEEDVOLUME_ENABLE; 166130803Smarcel 167130803Smarcel f->data = info; 168130803Smarcel m = feeder_matrix_default_channel_map(info->channels); 169130803Smarcel if (m == NULL) { 170130803Smarcel free(info, M_DEVBUF); 171130803Smarcel return (EINVAL); 172130803Smarcel } 173130803Smarcel 174130803Smarcel ret = feeder_volume_apply_matrix(f, m); 175130803Smarcel if (ret != 0) 176130803Smarcel free(info, M_DEVBUF); 177130803Smarcel 178130803Smarcel return (ret); 179130803Smarcel } 180130803Smarcel } 181130803Smarcel 182130803Smarcel return (EINVAL); 183130803Smarcel} 184130803Smarcel 185130803Smarcelstatic int 186130803Smarcelfeed_volume_free(struct pcm_feeder *f) 187130803Smarcel{ 188130803Smarcel struct feed_volume_info *info; 189130803Smarcel 190130803Smarcel info = f->data; 191130803Smarcel if (info != NULL) 192130803Smarcel free(info, M_DEVBUF); 193130803Smarcel 194130803Smarcel f->data = NULL; 195130803Smarcel 196130803Smarcel return (0); 197130803Smarcel} 198130803Smarcel 199130803Smarcelstatic int 200130803Smarcelfeed_volume_set(struct pcm_feeder *f, int what, int value) 201130803Smarcel{ 202130803Smarcel struct feed_volume_info *info; 203130803Smarcel struct pcmchan_matrix *m; 204130803Smarcel int ret; 205130803Smarcel 206130803Smarcel info = f->data; 207130803Smarcel ret = 0; 208130803Smarcel 209130803Smarcel switch (what) { 210130803Smarcel case FEEDVOLUME_CLASS: 211130803Smarcel if (value < SND_VOL_C_BEGIN || value > SND_VOL_C_END) 212130803Smarcel return (EINVAL); 213130803Smarcel info->volume_class = value; 214130803Smarcel break; 215130803Smarcel case FEEDVOLUME_CHANNELS: 216130803Smarcel if (value < SND_CHN_MIN || value > SND_CHN_MAX) 217130803Smarcel return (EINVAL); 218130803Smarcel m = feeder_matrix_default_channel_map(value); 219130803Smarcel if (m == NULL) 220130803Smarcel return (EINVAL); 221130803Smarcel ret = feeder_volume_apply_matrix(f, m); 222130803Smarcel break; 223130803Smarcel case FEEDVOLUME_STATE: 224130803Smarcel if (!(value == FEEDVOLUME_ENABLE || value == FEEDVOLUME_BYPASS)) 225130803Smarcel return (EINVAL); 226130803Smarcel info->state = value; 227130803Smarcel break; 228130803Smarcel default: 229130803Smarcel return (EINVAL); 230130803Smarcel break; 231130803Smarcel } 232130803Smarcel 233130803Smarcel return (ret); 234130803Smarcel} 235130803Smarcel 236130803Smarcelstatic int 237130803Smarcelfeed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, 238130803Smarcel uint32_t count, void *source) 239130803Smarcel{ 240130803Smarcel struct feed_volume_info *info; 241130803Smarcel uint32_t j, align; 242130803Smarcel int i, *vol, *matrix; 243130803Smarcel uint8_t *dst; 244130803Smarcel 245130803Smarcel /* 246130803Smarcel * Fetch filter data operation. 247130803Smarcel */ 248130803Smarcel info = f->data; 249130803Smarcel 250130803Smarcel if (info->state == FEEDVOLUME_BYPASS) 251130803Smarcel return (FEEDER_FEED(f->source, c, b, count, source)); 252130803Smarcel 253130803Smarcel vol = c->volume[SND_VOL_C_VAL(info->volume_class)]; 254130803Smarcel matrix = info->matrix; 255130803Smarcel 256130803Smarcel /* 257130803Smarcel * First, let see if we really need to apply gain at all. 258130803Smarcel */ 259130803Smarcel j = 0; 260130803Smarcel i = info->channels; 261130803Smarcel do { 262130803Smarcel if (vol[matrix[--i]] != SND_VOL_FLAT) { 263130803Smarcel j = 1; 264130803Smarcel break; 265130803Smarcel } 266130803Smarcel } while (i != 0); 267130803Smarcel 268130803Smarcel /* Nope, just bypass entirely. */ 269130803Smarcel if (j == 0) 270130803Smarcel return (FEEDER_FEED(f->source, c, b, count, source)); 271130803Smarcel 272130803Smarcel dst = b; 273130803Smarcel align = info->bps * info->channels; 274130803Smarcel 275130803Smarcel do { 276130803Smarcel if (count < align) 277130803Smarcel break; 278130803Smarcel 279130803Smarcel j = SND_FXDIV(FEEDER_FEED(f->source, c, dst, count, source), 280130803Smarcel align); 281130803Smarcel if (j == 0) 282130803Smarcel break; 283130803Smarcel 284130803Smarcel info->apply(vol, matrix, info->channels, dst, j); 285130803Smarcel 286130803Smarcel j *= align; 287130803Smarcel dst += j; 288130803Smarcel count -= j; 289130803Smarcel 290130803Smarcel } while (count != 0); 291130803Smarcel 292130803Smarcel return (dst - b); 293130803Smarcel} 294130803Smarcel 295130803Smarcelstatic struct pcm_feederdesc feeder_volume_desc[] = { 296130803Smarcel { FEEDER_VOLUME, 0, 0, 0, 0 }, 297130803Smarcel { 0, 0, 0, 0, 0 } 298130803Smarcel}; 299130803Smarcel 300130803Smarcelstatic kobj_method_t feeder_volume_methods[] = { 301130803Smarcel KOBJMETHOD(feeder_init, feed_volume_init), 302130803Smarcel KOBJMETHOD(feeder_free, feed_volume_free), 303130803Smarcel KOBJMETHOD(feeder_set, feed_volume_set), 304130803Smarcel KOBJMETHOD(feeder_feed, feed_volume_feed), 305130803Smarcel KOBJMETHOD_END 306130803Smarcel}; 307130803Smarcel 308130803SmarcelFEEDER_DECLARE(feeder_volume, NULL); 309130803Smarcel 310130803Smarcel/* Extern */ 311130803Smarcel 312130803Smarcel/* 313130803Smarcel * feeder_volume_apply_matrix(): For given matrix map, apply its configuration 314130803Smarcel * to feeder_volume matrix structure. There are 315130803Smarcel * possibilites that feeder_volume be inserted 316130803Smarcel * before or after feeder_matrix, which in this 317130803Smarcel * case feeder_volume must be in a good terms 318130803Smarcel * with _current_ matrix. 319130803Smarcel */ 320130803Smarcelint 321130803Smarcelfeeder_volume_apply_matrix(struct pcm_feeder *f, struct pcmchan_matrix *m) 322130803Smarcel{ 323130803Smarcel struct feed_volume_info *info; 324130803Smarcel uint32_t i; 325130803Smarcel 326130803Smarcel if (f == NULL || f->desc == NULL || f->desc->type != FEEDER_VOLUME || 327130803Smarcel f->data == NULL || m == NULL || m->channels < SND_CHN_MIN || 328130803Smarcel m->channels > SND_CHN_MAX) 329130803Smarcel return (EINVAL); 330130803Smarcel 331130803Smarcel info = f->data; 332130803Smarcel 333130803Smarcel for (i = 0; i < (sizeof(info->matrix) / sizeof(info->matrix[0])); i++) { 334130803Smarcel if (i < m->channels) 335130803Smarcel info->matrix[i] = m->map[i].type; 336130803Smarcel else 337130803Smarcel info->matrix[i] = SND_CHN_T_FL; 338130803Smarcel } 339130803Smarcel 340130803Smarcel info->channels = m->channels; 341130803Smarcel 342130803Smarcel return (0); 343130803Smarcel} 344130803Smarcel