vchan.c revision 78895
1240116Smarcel/* 2240116Smarcel * Copyright (c) 2001 Cameron Grant <gandalf@vilnya.demon.co.uk> 3240116Smarcel * All rights reserved. 4240116Smarcel * 5240116Smarcel * Redistribution and use in source and binary forms, with or without 6240116Smarcel * modification, are permitted provided that the following conditions 7240116Smarcel * are met: 8240116Smarcel * 1. Redistributions of source code must retain the above copyright 9240116Smarcel * notice, this list of conditions and the following disclaimer. 10240116Smarcel * 2. Redistributions in binary form must reproduce the above copyright 11240116Smarcel * notice, this list of conditions and the following disclaimer in the 12240116Smarcel * documentation and/or other materials provided with the distribution. 13240116Smarcel * 14240116Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15240116Smarcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16240116Smarcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17240116Smarcel * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18240116Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19240116Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20240116Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21240116Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22240116Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23240116Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24240116Smarcel * SUCH DAMAGE. 25240116Smarcel * 26240116Smarcel * $FreeBSD: head/sys/dev/sound/pcm/vchan.c 78895 2001-06-27 19:59:45Z cg $ 27240116Smarcel */ 28240116Smarcel 29240116Smarcel#include <dev/sound/pcm/sound.h> 30240116Smarcel#include <dev/sound/pcm/vchan.h> 31240116Smarcel#include "feeder_if.h" 32240116Smarcel 33240116Smarcelstruct vchinfo { 34240116Smarcel u_int32_t spd, fmt, blksz, bps, run; 35240116Smarcel struct pcm_channel *channel, *parent; 36240116Smarcel struct pcmchan_caps caps; 37240116Smarcel}; 38240116Smarcel 39240116Smarcelstatic u_int32_t vchan_fmt[] = { 40240116Smarcel AFMT_S16_LE, 41240116Smarcel AFMT_STEREO | AFMT_S16_LE, 42240116Smarcel 0 43240116Smarcel}; 44240116Smarcel 45240116Smarcel 46240116Smarcelstatic int 47240116Smarcelvchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count) 48240116Smarcel{ 49240116Smarcel /* 50240116Smarcel * to is the output buffer, tmp is the input buffer 51240116Smarcel * count is the number of 16bit samples to mix 52240116Smarcel */ 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 72static int 73feed_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 106static 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}; 111static kobj_method_t feeder_vchan_s16_methods[] = { 112 KOBJMETHOD(feeder_feed, feed_vchan_s16), 113 { 0, 0 } 114}; 115FEEDER_DECLARE(feeder_vchan_s16, 2, NULL); 116 117/************************************************************/ 118 119static void * 120vchan_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 138static int 139vchan_free(kobj_t obj, void *data) 140{ 141 return 0; 142} 143 144static int 145vchan_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 159static int 160vchan_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 170static int 171vchan_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 189static int 190vchan_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 204static struct pcmchan_caps * 205vchan_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 217static 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}; 227CHANNEL_DECLARE(vchan); 228 229/* virtual channel interface */ 230 231int 232vchan_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 285int 286vchan_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; 310gotch: 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 331static int 332sysctl_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; 379addskip: 380 } 381 snd_mtxunlock(d->lock); 382 return EBUSY; 383addok: 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; 401remok: 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 414int 415vchan_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