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
feed_mixer_apply(uint8_t * src,uint8_t * dst,uint32_t count,const uint32_t fmt)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
feed_mixer_init(struct pcm_feeder * f)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
feed_mixer_free(struct pcm_feeder * f)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
feed_mixer_set(struct pcm_feeder * f,int what,int value)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
feed_mixer_rec(struct pcm_channel * c)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
feed_mixer_feed(struct pcm_feeder * f,struct pcm_channel * c,uint8_t * b,uint32_t count,void * source)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