1 /* 2 * Copyright (c) 2001 Cameron Grant <gandalf@vilnya.demon.co.uk> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD$ 27 */ 28 29 #include <dev/sound/pcm/sound.h> 30 #include <dev/sound/pcm/vchan.h> 31 #include "feeder_if.h" 32 33 struct vchinfo { 34 u_int32_t spd, fmt, blksz, bps, run; 35 struct pcm_channel *channel, *parent; 36 struct pcmchan_caps caps; 37 }; 38 39 static u_int32_t vchan_fmt[] = { 40 AFMT_S16_LE, 41 AFMT_STEREO | AFMT_S16_LE, 42 0 43 }; 44 45 46 static int 47 vchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count) 48 { 49 /* 50 * to is the output buffer, tmp is the input buffer 51 * count is the number of 16bit samples to mix 52 */ 53 int i; 54 int x; 55 56 for(i = 0; i < count; i++) { 57 x = to[i]; 58 x += tmp[i]; 59 if (x < -32768) { 60 /* printf("%d + %d = %d (u)\n", to[i], tmp[i], x); */ 61 x = -32768; 62 } 63 if (x > 32767) { 64 /* printf("%d + %d = %d (o)\n", to[i], tmp[i], x); */ 65 x = 32767; 66 } 67 to[i] = x & 0x0000ffff; 68 } 69 return 0; 70 } 71 72 static int 73 feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) 74 { 75 /* we're going to abuse things a bit */ 76 struct snd_dbuf *src = source; 77 struct pcmchan_children *cce; 78 struct pcm_channel *ch; 79 int16_t *tmp, *dst; 80 unsigned int cnt; 81 82 KASSERT(sndbuf_getsize(src) >= count, ("bad bufsize")); 83 count &= ~1; 84 bzero(b, count); 85 86 /* 87 * we are going to use our source as a temporary buffer since it's 88 * got no other purpose. we obtain our data by traversing the channel 89 * list of children and calling vchan_mix_* to mix count bytes from each 90 * into our destination buffer, b 91 */ 92 dst = (int16_t *)b; 93 tmp = (int16_t *)sndbuf_getbuf(src); 94 bzero(tmp, count); 95 SLIST_FOREACH(cce, &c->children, link) { 96 ch = cce->channel; 97 if (ch->flags & CHN_F_TRIGGERED) { 98 cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft); 99 vchan_mix_s16(dst, tmp, cnt / 2); 100 } 101 } 102 103 return count; 104 } 105 106 static struct pcm_feederdesc feeder_vchan_s16_desc[] = { 107 {FEEDER_MIXER, AFMT_S16_LE, AFMT_S16_LE, 0}, 108 {FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, 109 {0}, 110 }; 111 static kobj_method_t feeder_vchan_s16_methods[] = { 112 KOBJMETHOD(feeder_feed, feed_vchan_s16), 113 { 0, 0 } 114 }; 115 FEEDER_DECLARE(feeder_vchan_s16, 2, NULL); 116 117 /************************************************************/ 118 119 static void * 120 vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) 121 { 122 struct vchinfo *ch; 123 struct pcm_channel *parent = devinfo; 124 125 KASSERT(dir == PCMDIR_PLAY, ("vchan_init: bad direction")); 126 ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); 127 ch->parent = parent; 128 ch->channel = c; 129 ch->fmt = AFMT_U8; 130 ch->spd = DSP_DEFAULT_SPEED; 131 ch->blksz = 2048; 132 133 c->flags |= CHN_F_VIRTUAL; 134 135 return ch; 136 } 137 138 static int 139 vchan_free(kobj_t obj, void *data) 140 { 141 return 0; 142 } 143 144 static int 145 vchan_setformat(kobj_t obj, void *data, u_int32_t format) 146 { 147 struct vchinfo *ch = data; 148 struct pcm_channel *parent = ch->parent; 149 150 ch->fmt = format; 151 ch->bps = 1; 152 ch->bps <<= (ch->fmt & AFMT_STEREO)? 1 : 0; 153 ch->bps <<= (ch->fmt & AFMT_16BIT)? 1 : 0; 154 ch->bps <<= (ch->fmt & AFMT_32BIT)? 2 : 0; 155 chn_notify(parent, CHN_N_FORMAT); 156 return 0; 157 } 158 159 static int 160 vchan_setspeed(kobj_t obj, void *data, u_int32_t speed) 161 { 162 struct vchinfo *ch = data; 163 struct pcm_channel *parent = ch->parent; 164 165 ch->spd = speed; 166 chn_notify(parent, CHN_N_RATE); 167 return speed; 168 } 169 170 static int 171 vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) 172 { 173 struct vchinfo *ch = data; 174 struct pcm_channel *parent = ch->parent; 175 int prate, crate; 176 177 ch->blksz = blocksize; 178 chn_notify(parent, CHN_N_BLOCKSIZE); 179 180 crate = ch->spd * ch->bps; 181 prate = sndbuf_getspd(parent->bufhard) * sndbuf_getbps(parent->bufhard); 182 blocksize = sndbuf_getblksz(parent->bufhard); 183 blocksize *= prate; 184 blocksize /= crate; 185 186 return blocksize; 187 } 188 189 static int 190 vchan_trigger(kobj_t obj, void *data, int go) 191 { 192 struct vchinfo *ch = data; 193 struct pcm_channel *parent = ch->parent; 194 195 if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) 196 return 0; 197 198 ch->run = (go == PCMTRIG_START)? 1 : 0; 199 chn_notify(parent, CHN_N_TRIGGER); 200 201 return 0; 202 } 203 204 static struct pcmchan_caps * 205 vchan_getcaps(kobj_t obj, void *data) 206 { 207 struct vchinfo *ch = data; 208 209 ch->caps.minspeed = sndbuf_getspd(ch->parent->bufhard); 210 ch->caps.maxspeed = ch->caps.minspeed; 211 ch->caps.fmtlist = vchan_fmt; 212 ch->caps.caps = 0; 213 214 return &ch->caps; 215 } 216 217 static kobj_method_t vchan_methods[] = { 218 KOBJMETHOD(channel_init, vchan_init), 219 KOBJMETHOD(channel_free, vchan_free), 220 KOBJMETHOD(channel_setformat, vchan_setformat), 221 KOBJMETHOD(channel_setspeed, vchan_setspeed), 222 KOBJMETHOD(channel_setblocksize, vchan_setblocksize), 223 KOBJMETHOD(channel_trigger, vchan_trigger), 224 KOBJMETHOD(channel_getcaps, vchan_getcaps), 225 { 0, 0 } 226 }; 227 CHANNEL_DECLARE(vchan); 228 229 /* virtual channel interface */ 230 231 int 232 vchan_create(struct pcm_channel *parent) 233 { 234 struct snddev_info *d = parent->parentsnddev; 235 struct pcmchan_children *pce; 236 struct pcm_channel *child; 237 int err, first; 238 239 CHN_LOCK(parent); 240 if (!(parent->flags & CHN_F_BUSY)) { 241 CHN_UNLOCK(parent); 242 return EBUSY; 243 } 244 245 pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO); 246 if (!pce) { 247 CHN_UNLOCK(parent); 248 return ENOMEM; 249 } 250 251 /* create a new playback channel */ 252 child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent); 253 if (!child) { 254 free(pce, M_DEVBUF); 255 CHN_UNLOCK(parent); 256 return ENODEV; 257 } 258 259 first = SLIST_EMPTY(&parent->children); 260 /* add us to our parent channel's children */ 261 pce->channel = child; 262 SLIST_INSERT_HEAD(&parent->children, pce, link); 263 CHN_UNLOCK(parent); 264 265 /* add us to our grandparent's channel list */ 266 err = pcm_chn_add(d, child, !first); 267 if (err) { 268 pcm_chn_destroy(child); 269 free(pce, M_DEVBUF); 270 } 271 272 /* XXX gross ugly hack, kill murder death */ 273 if (first && !err) { 274 err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE); 275 if (err) 276 printf("chn_reset: %d\n", err); 277 err = chn_setspeed(parent, 44100); 278 if (err) 279 printf("chn_setspeed: %d\n", err); 280 } 281 282 return err; 283 } 284 285 int 286 vchan_destroy(struct pcm_channel *c) 287 { 288 struct pcm_channel *parent = c->parentchannel; 289 struct snddev_info *d = parent->parentsnddev; 290 struct pcmchan_children *pce; 291 int err, last; 292 293 CHN_LOCK(parent); 294 if (!(parent->flags & CHN_F_BUSY)) { 295 CHN_UNLOCK(parent); 296 return EBUSY; 297 } 298 if (SLIST_EMPTY(&parent->children)) { 299 CHN_UNLOCK(parent); 300 return EINVAL; 301 } 302 303 /* remove us from our parent's children list */ 304 SLIST_FOREACH(pce, &parent->children, link) { 305 if (pce->channel == c) 306 goto gotch; 307 } 308 CHN_UNLOCK(parent); 309 return EINVAL; 310 gotch: 311 SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); 312 free(pce, M_DEVBUF); 313 314 last = SLIST_EMPTY(&parent->children); 315 if (last) 316 parent->flags &= ~CHN_F_BUSY; 317 318 /* remove us from our grantparent's channel list */ 319 err = pcm_chn_remove(d, c, !last); 320 if (err) 321 return err; 322 323 CHN_UNLOCK(parent); 324 /* destroy ourselves */ 325 err = pcm_chn_destroy(c); 326 327 return err; 328 } 329 330 #ifdef SND_DYNSYSCTL 331 static int 332 sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) 333 { 334 struct snddev_info *d; 335 struct snddev_channel *sce; 336 struct pcm_channel *c; 337 int err, oldcnt, newcnt, cnt; 338 339 d = oidp->oid_arg1; 340 341 snd_mtxlock(d->lock); 342 cnt = 0; 343 SLIST_FOREACH(sce, &d->channels, link) { 344 c = sce->channel; 345 if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL)) 346 cnt++; 347 } 348 oldcnt = cnt; 349 newcnt = cnt; 350 351 err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req); 352 if (err == 0 && req->newptr != NULL) { 353 if (newcnt < 0 || newcnt > SND_MAXVCHANS) { 354 snd_mtxunlock(d->lock); 355 return EINVAL; 356 } 357 358 if (newcnt > cnt) { 359 /* add new vchans - find a parent channel first */ 360 SLIST_FOREACH(sce, &d->channels, link) { 361 c = sce->channel; 362 /* not a candidate if not a play channel */ 363 if (c->direction != PCMDIR_PLAY) 364 goto addskip; 365 /* not a candidate if a virtual channel */ 366 if (c->flags & CHN_F_VIRTUAL) 367 goto addskip; 368 /* not a candidate if it's in use */ 369 if ((c->flags & CHN_F_BUSY) && (SLIST_EMPTY(&c->children))) 370 goto addskip; 371 /* 372 * if we get here we're a nonvirtual play channel, and either 373 * 1) not busy 374 * 2) busy with children, not directly open 375 * 376 * thus we can add children 377 */ 378 goto addok; 379 addskip: 380 } 381 snd_mtxunlock(d->lock); 382 return EBUSY; 383 addok: 384 c->flags |= CHN_F_BUSY; 385 while (err == 0 && newcnt > cnt) { 386 err = vchan_create(c); 387 if (err == 0) 388 cnt++; 389 } 390 if (SLIST_EMPTY(&c->children)) 391 c->flags &= ~CHN_F_BUSY; 392 } else if (newcnt < cnt) { 393 while (err == 0 && newcnt < cnt) { 394 SLIST_FOREACH(sce, &d->channels, link) { 395 c = sce->channel; 396 if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL) 397 goto remok; 398 } 399 snd_mtxunlock(d->lock); 400 return EINVAL; 401 remok: 402 err = vchan_destroy(c); 403 if (err == 0) 404 cnt--; 405 } 406 } 407 } 408 409 snd_mtxunlock(d->lock); 410 return err; 411 } 412 #endif 413 414 int 415 vchan_initsys(struct snddev_info *d) 416 { 417 #ifdef SND_DYNSYSCTL 418 SYSCTL_ADD_PROC(&d->sysctl_tree, SYSCTL_CHILDREN(d->sysctl_tree_top), 419 OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), 420 sysctl_hw_snd_vchans, "I", "") 421 #endif 422 return 0; 423 } 424 425 426