1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2008-2009 Ariff Abdullah <ariff@FreeBSD.org> 5 * All rights reserved. 6 * Copyright (c) 2024-2025 The FreeBSD Foundation 7 * 8 * Portions of this software were developed by Christos Margiolis 9 * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #ifdef _KERNEL 34 #ifdef HAVE_KERNEL_OPTION_HEADERS 35 #include "opt_snd.h" 36 #endif 37 #include <dev/sound/pcm/sound.h> 38 #include <dev/sound/pcm/pcm.h> 39 #include <dev/sound/pcm/vchan.h> 40 #include "feeder_if.h" 41 42 #define SND_USE_FXDIV 43 #include "snd_fxdiv_gen.h" 44 #endif 45 46 #undef SND_FEEDER_MULTIFORMAT 47 #define SND_FEEDER_MULTIFORMAT 1 48 49 struct feed_mixer_info { 50 uint32_t format; 51 uint32_t channels; 52 int bps; 53 }; 54 55 __always_inline static void 56 feed_mixer_apply(uint8_t *src, uint8_t *dst, uint32_t count, const uint32_t fmt) 57 { 58 intpcm32_t z; 59 intpcm_t x, y; 60 61 src += count; 62 dst += count; 63 64 do { 65 src -= AFMT_BPS(fmt); 66 dst -= AFMT_BPS(fmt); 67 count -= AFMT_BPS(fmt); 68 x = pcm_sample_read_calc(src, fmt); 69 y = pcm_sample_read_calc(dst, fmt); 70 z = INTPCM_T(x) + y; 71 x = pcm_clamp_calc(z, fmt); 72 pcm_sample_write(dst, x, fmt); 73 } while (count != 0); 74 } 75 76 static int 77 feed_mixer_init(struct pcm_feeder *f) 78 { 79 struct feed_mixer_info *info; 80 81 if (f->desc->in != f->desc->out) 82 return (EINVAL); 83 84 info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO); 85 if (info == NULL) 86 return (ENOMEM); 87 88 info->format = AFMT_ENCODING(f->desc->in); 89 info->channels = AFMT_CHANNEL(f->desc->in); 90 info->bps = AFMT_BPS(f->desc->in); 91 92 f->data = info; 93 94 return (0); 95 } 96 97 static int 98 feed_mixer_free(struct pcm_feeder *f) 99 { 100 struct feed_mixer_info *info; 101 102 info = f->data; 103 if (info != NULL) 104 free(info, M_DEVBUF); 105 106 f->data = NULL; 107 108 return (0); 109 } 110 111 static int 112 feed_mixer_set(struct pcm_feeder *f, int what, int value) 113 { 114 struct feed_mixer_info *info; 115 116 info = f->data; 117 118 switch (what) { 119 case FEEDMIXER_CHANNELS: 120 if (value < SND_CHN_MIN || value > SND_CHN_MAX) 121 return (EINVAL); 122 info->channels = (uint32_t)value; 123 break; 124 default: 125 return (EINVAL); 126 } 127 128 return (0); 129 } 130 131 static __inline int 132 feed_mixer_rec(struct pcm_channel *c) 133 { 134 struct pcm_channel *ch; 135 struct snd_dbuf *b, *bs; 136 uint32_t cnt, maxfeed; 137 int rdy; 138 139 /* 140 * Reset ready and moving pointer. We're not using bufsoft 141 * anywhere since its sole purpose is to become the primary 142 * distributor for the recorded buffer and also as an interrupt 143 * threshold progress indicator. 144 */ 145 b = c->bufsoft; 146 b->rp = 0; 147 b->rl = 0; 148 cnt = sndbuf_getsize(b); 149 maxfeed = SND_FXROUND(SND_FXDIV_MAX, sndbuf_getalign(b)); 150 151 do { 152 cnt = FEEDER_FEED(c->feeder->source, c, b->tmpbuf, 153 min(cnt, maxfeed), c->bufhard); 154 if (cnt != 0) { 155 sndbuf_acquire(b, b->tmpbuf, cnt); 156 cnt = sndbuf_getfree(b); 157 } 158 } while (cnt != 0); 159 160 /* Not enough data */ 161 if (b->rl < sndbuf_getalign(b)) { 162 b->rl = 0; 163 return (0); 164 } 165 166 /* 167 * Keep track of ready and moving pointer since we will use 168 * bufsoft over and over again, pretending nothing has happened. 169 */ 170 rdy = b->rl; 171 172 CHN_FOREACH(ch, c, children.busy) { 173 CHN_LOCK(ch); 174 if (CHN_STOPPED(ch) || (ch->flags & CHN_F_DIRTY)) { 175 CHN_UNLOCK(ch); 176 continue; 177 } 178 #ifdef SND_DEBUG 179 if ((c->flags & CHN_F_DIRTY) && VCHAN_SYNC_REQUIRED(ch)) { 180 if (vchan_sync(ch) != 0) { 181 CHN_UNLOCK(ch); 182 continue; 183 } 184 } 185 #endif 186 bs = ch->bufsoft; 187 if (ch->flags & CHN_F_MMAP) 188 sndbuf_dispose(bs, NULL, sndbuf_getready(bs)); 189 cnt = sndbuf_getfree(bs); 190 if (cnt < sndbuf_getalign(bs)) { 191 CHN_UNLOCK(ch); 192 continue; 193 } 194 maxfeed = SND_FXROUND(SND_FXDIV_MAX, sndbuf_getalign(bs)); 195 do { 196 cnt = FEEDER_FEED(ch->feeder, ch, bs->tmpbuf, 197 min(cnt, maxfeed), b); 198 if (cnt != 0) { 199 sndbuf_acquire(bs, bs->tmpbuf, cnt); 200 cnt = sndbuf_getfree(bs); 201 } 202 } while (cnt != 0); 203 /* 204 * Not entirely flushed out... 205 */ 206 if (b->rl != 0) 207 ch->xruns++; 208 CHN_UNLOCK(ch); 209 /* 210 * Rewind buffer position for next virtual channel. 211 */ 212 b->rp = 0; 213 b->rl = rdy; 214 } 215 216 /* 217 * Set ready pointer to indicate that our children are ready 218 * to be woken up, also as an interrupt threshold progress 219 * indicator. 220 */ 221 b->rl = 1; 222 223 c->flags &= ~CHN_F_DIRTY; 224 225 /* 226 * Return 0 to bail out early from sndbuf_feed() loop. 227 * No need to increase feedcount counter since part of this 228 * feeder chains already include feed_root(). 229 */ 230 return (0); 231 } 232 233 static int 234 feed_mixer_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, 235 uint32_t count, void *source) 236 { 237 struct feed_mixer_info *info; 238 struct snd_dbuf *src = source; 239 struct pcm_channel *ch; 240 uint32_t cnt, mcnt, rcnt, sz; 241 int passthrough; 242 uint8_t *tmp; 243 244 if (c->direction == PCMDIR_REC) 245 return (feed_mixer_rec(c)); 246 247 sz = sndbuf_getsize(src); 248 if (sz < count) 249 count = sz; 250 251 info = f->data; 252 sz = info->bps * info->channels; 253 count = SND_FXROUND(count, sz); 254 if (count < sz) 255 return (0); 256 257 /* 258 * We are going to use our source as a temporary buffer since it's 259 * got no other purpose. We obtain our data by traversing the channel 260 * list of children and calling mixer function to mix count bytes from 261 * each into our destination buffer, b. 262 */ 263 tmp = sndbuf_getbuf(src); 264 rcnt = 0; 265 mcnt = 0; 266 passthrough = 0; /* 'passthrough' / 'exclusive' marker */ 267 268 CHN_FOREACH(ch, c, children.busy) { 269 CHN_LOCK(ch); 270 if (CHN_STOPPED(ch) || (ch->flags & CHN_F_DIRTY)) { 271 CHN_UNLOCK(ch); 272 continue; 273 } 274 #ifdef SND_DEBUG 275 if ((c->flags & CHN_F_DIRTY) && VCHAN_SYNC_REQUIRED(ch)) { 276 if (vchan_sync(ch) != 0) { 277 CHN_UNLOCK(ch); 278 continue; 279 } 280 } 281 #endif 282 if ((ch->flags & CHN_F_MMAP) && !(ch->flags & CHN_F_CLOSING)) 283 sndbuf_acquire(ch->bufsoft, NULL, 284 sndbuf_getfree(ch->bufsoft)); 285 if (c->flags & CHN_F_PASSTHROUGH) { 286 /* 287 * Passthrough. Dump the first digital/passthrough 288 * channel into destination buffer, and the rest into 289 * nothingness (mute effect). 290 */ 291 if (passthrough == 0 && 292 (ch->format & AFMT_PASSTHROUGH)) { 293 rcnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch, 294 b, count, ch->bufsoft), sz); 295 passthrough = 1; 296 } else 297 FEEDER_FEED(ch->feeder, ch, tmp, count, 298 ch->bufsoft); 299 } else if (c->flags & CHN_F_EXCLUSIVE) { 300 /* 301 * Exclusive. Dump the first 'exclusive' channel into 302 * destination buffer, and the rest into nothingness 303 * (mute effect). 304 */ 305 if (passthrough == 0 && (ch->flags & CHN_F_EXCLUSIVE)) { 306 rcnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch, 307 b, count, ch->bufsoft), sz); 308 passthrough = 1; 309 } else 310 FEEDER_FEED(ch->feeder, ch, tmp, count, 311 ch->bufsoft); 312 } else { 313 if (rcnt == 0) { 314 rcnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch, 315 b, count, ch->bufsoft), sz); 316 mcnt = count - rcnt; 317 } else { 318 cnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch, 319 tmp, count, ch->bufsoft), sz); 320 if (cnt != 0) { 321 if (mcnt != 0) { 322 memset(b + rcnt, 323 sndbuf_zerodata( 324 f->desc->out), mcnt); 325 mcnt = 0; 326 } 327 switch (info->format) { 328 case AFMT_S16_NE: 329 feed_mixer_apply(tmp, b, cnt, 330 AFMT_S16_NE); 331 break; 332 case AFMT_S24_NE: 333 feed_mixer_apply(tmp, b, cnt, 334 AFMT_S24_NE); 335 break; 336 case AFMT_S32_NE: 337 feed_mixer_apply(tmp, b, cnt, 338 AFMT_S32_NE); 339 break; 340 default: 341 feed_mixer_apply(tmp, b, cnt, 342 info->format); 343 break; 344 } 345 if (cnt > rcnt) 346 rcnt = cnt; 347 } 348 } 349 } 350 CHN_UNLOCK(ch); 351 } 352 353 if (++c->feedcount == 0) 354 c->feedcount = 2; 355 356 c->flags &= ~CHN_F_DIRTY; 357 358 return (rcnt); 359 } 360 361 static struct pcm_feederdesc feeder_mixer_desc[] = { 362 { FEEDER_MIXER, 0, 0, 0, 0 }, 363 { 0, 0, 0, 0, 0 } 364 }; 365 366 static kobj_method_t feeder_mixer_methods[] = { 367 KOBJMETHOD(feeder_init, feed_mixer_init), 368 KOBJMETHOD(feeder_free, feed_mixer_free), 369 KOBJMETHOD(feeder_set, feed_mixer_set), 370 KOBJMETHOD(feeder_feed, feed_mixer_feed), 371 KOBJMETHOD_END 372 }; 373 374 FEEDER_DECLARE(feeder_mixer, NULL); 375