vchan.c revision 78214
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: head/sys/dev/sound/pcm/vchan.c 78214 2001-06-14 13:31:30Z cg $ 27 */ 28 29#include <dev/sound/pcm/sound.h> 30#include <dev/sound/pcm/vchan.h> 31#include "feeder_if.h" 32 33struct vchinfo { 34 u_int32_t spd, fmt, blksz, bps, run; 35 struct pcm_channel *channel, *parent; 36 struct pcmchan_caps caps; 37}; 38 39static u_int32_t vchan_fmt[] = { 40 AFMT_S16_LE, 41 AFMT_STEREO | AFMT_S16_LE, 42 0 43}; 44 45 46static int 47vchan_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 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); 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; 292 293 /* remove us from our grantparent's channel list */ 294 err = pcm_chn_remove(d, c); 295 if (err) 296 return err; 297 298 CHN_LOCK(parent); 299 if (!(parent->flags & CHN_F_BUSY)) { 300 CHN_UNLOCK(parent); 301 return EBUSY; 302 } 303 if (SLIST_EMPTY(&parent->children)) { 304 CHN_UNLOCK(parent); 305 return EINVAL; 306 } 307 308 /* remove us from our parent's children list */ 309 SLIST_FOREACH(pce, &parent->children, link) { 310 if (pce->channel == c) 311 goto gotch; 312 } 313 CHN_UNLOCK(parent); 314 return EINVAL; 315gotch: 316 SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); 317 free(pce, M_DEVBUF); 318 319 if (SLIST_EMPTY(&parent->children)) 320 parent->flags &= ~CHN_F_BUSY; 321 CHN_UNLOCK(parent); 322 /* destroy ourselves */ 323 err = pcm_chn_destroy(c); 324 325 return err; 326} 327 328#ifdef SND_DYNSYSCTL 329static int 330sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) 331{ 332 struct snddev_info *d; 333 struct snddev_channel *sce; 334 struct pcm_channel *c; 335 int err, oldcnt, newcnt, cnt; 336 337 d = oidp->oid_arg1; 338 339 snd_mtxlock(d->lock); 340 cnt = 0; 341 SLIST_FOREACH(sce, &d->channels, link) { 342 c = sce->channel; 343 if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL)) 344 cnt++; 345 } 346 oldcnt = cnt; 347 newcnt = cnt; 348 349 err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req); 350 if (err == 0 && req->newptr != NULL) { 351 if (newcnt > cnt) { 352 /* add new vchans - find a parent channel first */ 353 SLIST_FOREACH(sce, &d->channels, link) { 354 c = sce->channel; 355 /* not a candidate if not a play channel */ 356 if (c->direction != PCMDIR_PLAY) 357 goto addskip; 358 /* not a candidate if a virtual channel */ 359 if (c->flags & CHN_F_VIRTUAL) 360 goto addskip; 361 /* not a candidate if it's in use */ 362 if ((c->flags & CHN_F_BUSY) && (SLIST_EMPTY(&c->children))) 363 goto addskip; 364 /* 365 * if we get here we're a nonvirtual play channel, and either 366 * 1) not busy 367 * 2) busy with children, not directly open 368 * 369 * thus we can add children 370 */ 371 goto addok; 372addskip: 373 } 374 snd_mtxunlock(d->lock); 375 return EBUSY; 376addok: 377 c->flags |= CHN_F_BUSY; 378 while (err == 0 && newcnt > cnt) { 379 err = vchan_create(c); 380 if (err == 0) 381 cnt++; 382 } 383 if (SLIST_EMPTY(&c->children)) 384 c->flags &= ~CHN_F_BUSY; 385 } else if (newcnt < cnt) { 386 while (err == 0 && newcnt < cnt) { 387 SLIST_FOREACH(sce, &d->channels, link) { 388 c = sce->channel; 389 if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL) 390 goto remok; 391 } 392 snd_mtxunlock(d->lock); 393 return EINVAL; 394remok: 395 err = vchan_destroy(c); 396 if (err == 0) 397 cnt--; 398 } 399 } 400 } 401 402 snd_mtxunlock(d->lock); 403 return err; 404} 405#endif 406 407int 408vchan_initsys(struct snddev_info *d) 409{ 410#ifdef SND_DYNSYSCTL 411 SYSCTL_ADD_PROC(&d->sysctl_tree, SYSCTL_CHILDREN(d->sysctl_tree_top), 412 OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), 413 sysctl_hw_snd_vchans, "I", "") 414#endif 415 return 0; 416} 417 418 419