xref: /freebsd/sys/dev/sound/pcm/feeder_mixer.c (revision b6420b5ea5bcdeb859a2b3357e5dbaafe7aaff88)
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