📄 sound.c
字号:
{ int bytes, count; bytes = *(int *)arg & 0xffff ; count = ( *(int *)arg >> 16) & 0xffff ; if (bytes > 15) bytes = 15 ; bytes = 1 << bytes ; if (bytes <= 1) { /* means no blocks */ d->flags &= ~SND_F_HAS_SIZE ; } else { RANGE (bytes, 40, d->dbuf_out.bufsize /4); d->play_blocksize = d->rec_blocksize = bytes & ~3 ; /* align to multiple of 4 */ d->flags |= SND_F_HAS_SIZE ; } splx(s); ask_init(d);#if 0 /* XXX todo: set the buffer size to the # of fragments */ count = d->dbuf_in.bufsize / d->play_blocksize ; bytes = ffs(d->play_blocksize) - 1; /* * don't change arg, since it's fake anyways and some * programs might fail if we do. */ *(int *)arg = (count << 16) | bytes ;#endif } break ; case SNDCTL_DSP_GETISPACE: /* return space available in the input queue */ { audio_buf_info *a = (audio_buf_info *)arg; snd_dbuf *b = &(d->dbuf_in); if (b->dl) dsp_rd_dmaupdate( b ); a->bytes = d->dbuf_in.fl ; a->fragments = 1 ; a->fragstotal = b->bufsize / d->rec_blocksize ; a->fragsize = d->rec_blocksize ; } break ; case SNDCTL_DSP_GETOSPACE: /* return space available in the output queue */ { audio_buf_info *a = (audio_buf_info *)arg; snd_dbuf *b = &(d->dbuf_out); if (b->dl) dsp_wr_dmaupdate( b ); a->bytes = d->dbuf_out.fl ; a->fragments = 1 ; a->fragstotal = b->bufsize / d->play_blocksize ; a->fragsize = d->play_blocksize ; } break ; case SNDCTL_DSP_GETIPTR: { count_info *a = (count_info *)arg; snd_dbuf *b = &(d->dbuf_in); if (b->dl) dsp_rd_dmaupdate( b ); a->bytes = b->total; a->blocks = (b->total - b->prev_total + d->rec_blocksize -1 ) / d->rec_blocksize ; a->ptr = b->fp ; /* XXX not sure... */ b->prev_total = b->total ; } break; case SNDCTL_DSP_GETOPTR: { count_info *a = (count_info *)arg; snd_dbuf *b = &(d->dbuf_out); if (b->dl) dsp_wr_dmaupdate( b ); a->bytes = b->total; a->blocks = (b->total - b->prev_total /* +d->play_blocksize -1*/ ) / d->play_blocksize ; a->ptr = b->rp ; /* XXX not sure... */ b->prev_total = b->total ; } break; case SNDCTL_DSP_GETCAPS : *(int *) arg = 0x0 ; /* revision */ if (FULL_DUPLEX(d)) *(int *) arg |= DSP_CAP_DUPLEX ; *(int *) arg |= DSP_CAP_REALTIME ; break ; case SOUND_PCM_READ_BITS: if (d->play_fmt == AFMT_S16_LE) *(int *) arg = 16 ; else *(int *) arg = 8 ; break ; /* * mixer calls */ case SOUND_MIXER_READ_DEVMASK : case SOUND_MIXER_READ_CAPS : case SOUND_MIXER_READ_STEREODEVS : *(int *)arg = d->mix_devs; break ; case SOUND_MIXER_READ_RECMASK : *(int *)arg = d->mix_rec_devs; break ; case SOUND_MIXER_READ_RECSRC : *(int *)arg = d->mix_recsrc ; break; default: DEB(printf("default ioctl snd%d subdev %d fn 0x%08x fail\n", unit, dev & 0xf, cmd)); ret = EINVAL; break ; } splx(s); return ret ;}/* * we use the name 'select', but the new "poll" interface this is * really sndpoll. Second arg for poll is not "rw" but "events" */intsndselect(dev_t i_dev, int rw, struct proc * p){ int dev, unit, c = 1 /* default: success */ ; snddev_info *d ; u_long flags; dev = minor(i_dev); d = get_snddev_info(dev, &unit); DEB(printf("sndselect dev 0x%04x rw 0x%08x\n",i_dev, rw)); if (d == NULL ) /* should not happen! */ return (ENXIO) ; if (d->select == NULL)#if __FreeBSD__ >= 3 return ( (rw & (POLLIN|POLLOUT|POLLRDNORM|POLLWRNORM)) | POLLHUP);#else return 1 ; /* always success ? */#endif else if (d->select != sndselect ) return d->select(i_dev, rw, p); else { /* handle it here with the generic code */ /* * if the user selected a block size, then we want to use the * device as a block device, and select will return ready when * we have a full block. * In all other cases, select will return when 1 byte is ready. */ int lim = 1;#if __FreeBSD__ >= 3 int revents = 0 ; if (rw & (POLLOUT | POLLWRNORM) ) {#else if (rw == FWRITE) {#endif if ( d->flags & SND_F_HAS_SIZE ) lim = d->play_blocksize ; /* XXX fix the test here for half duplex devices */ if (1 /* write is compatible with current mode */) { flags = spltty(); if (d->dbuf_out.dl) dsp_wr_dmaupdate(&(d->dbuf_out)); c = d->dbuf_out.fl ; if (c < lim) /* no space available */ selrecord(p, & (d->wsel));#if __FreeBSD__ >= 3 else revents |= rw & (POLLOUT | POLLWRNORM);#endif splx(flags); }#if __FreeBSD__ >= 3 } if (rw & (POLLIN | POLLRDNORM)) {#else return c < lim ? 0 : 1 ; } else if (rw == FREAD) {#endif if ( d->flags & SND_F_HAS_SIZE ) lim = d->rec_blocksize ; /* XXX fix the test here */ if (1 /* read is compatible with current mode */) { flags = spltty(); if ( d->dbuf_in.dl == 0 ) /* dma idle, restart it */ dsp_rdintr(d); else dsp_rd_dmaupdate(&(d->dbuf_in)); c = d->dbuf_in.rl ; if (c < lim) /* no data available */ selrecord(p, & (d->rsel));#if __FreeBSD__ >= 3 else revents |= rw & (POLLIN | POLLRDNORM);#endif splx(flags); } DEB(printf("sndselect on read: %d >= %d flags 0x%08x\n", c, lim, d->flags)); return c < lim ? 0 : 1 ;#if __FreeBSD__ >= 3 } return revents;#else } else { DDB(printf("select on exceptions, unimplemented\n")); return 1; }#endif } return ENXIO ; /* notreached */}/* * The mmap interface allows access to the play and read buffer, * plus the device descriptor. * The various blocks are accessible at the following offsets: * * 0x00000000 ( 0 ) : write buffer ; * 0x01000000 (16 MB) : read buffer ; * 0x02000000 (32 MB) : device descriptor (dangerous!) * * WARNING: the mmap routines assume memory areas are aligned. This * is true (probably) for the dma buffers, but likely false for the * device descriptor. As a consequence, we do not know where it is * located in the requested area. */#include <sys/mman.h>#include <vm/vm.h> #include <vm/vm_kern.h> #include <vm/vm_param.h>#include <vm/pmap.h> #include <vm/vm_extern.h>static intsndmmap(dev_t dev, vm_offset_t offset, int nprot){ snddev_info *d = get_snddev_info(dev, NULL); DEB(printf("sndmmap d 0x%p dev 0x%04x ofs 0x%08x nprot 0x%08x\n", d, dev, offset, nprot)); if (d == NULL || nprot & PROT_EXEC || offset < 0) return -1 ; /* forbidden */ if (offset >= d->dbuf_out.bufsize && (nprot & PROT_WRITE) ) return -1 ; /* can only write to the first block */ if (offset < d->dbuf_out.bufsize) return i386_btop(vtophys(d->dbuf_out.buf + offset)); offset -= 1 << 24; if ( (offset >= 0) && (offset < d->dbuf_in.bufsize)) return i386_btop(vtophys(d->dbuf_in.buf + offset)); offset -= 1 << 24; if ( (offset >= 0) && (offset < 0x2000)) { return i386_btop(vtophys( ((int)d & ~0xfff) + offset)); } return -1 ;}/* * ask_init sets the init flag in the device descriptor, and * possibly calls the appropriate callback routine, returning 1 * if the callback was successful. This enables ioctls handler for * rw parameters to read back the updated value. * Since the init callback can be slow, ask_init() should be called * with interrupts enabled. */intask_init(snddev_info *d){ u_long s; if ( d->callback == NULL ) return 0 ; s = spltty(); if ( d->flags & SND_F_PENDING_IO || d->dbuf_out.dl || d->dbuf_in.dl ) { /* cannot do it now, record the request and return */ d->flags |= SND_F_INIT ; splx(s); return 0 ; } else { splx(s); d->callback(d, SND_CB_INIT ); return 1; }}/* * these are the functions for the soundstat device. We copy parameters * from the device info structure to static variables, and from there * back to the structure when done. */static voidinit_status(snddev_info *d){ /* * Write the status information to the status_buf and update * status_len. There is a limit of SNDSTAT_BUF_SIZE bytes for the data. */ int i; if (status_len != 0) /* only do init once */ return ; sprintf(status_buf, "FreeBSD Audio Driver (981022) " __DATE__ " " __TIME__ "\n" "Installed devices:\n"); for (i = 0; i < NPCM_MAX; i++) { if (pcm_info[i].open) sprintf(status_buf + strlen(status_buf), "pcm%d: <%s> at 0x%x irq %d dma %d:%d\n", i, pcm_info[i].name, pcm_info[i].io_base, pcm_info[i].irq, pcm_info[i].dbuf_out.chan, pcm_info[i].dbuf_in.chan); if (midi_info[i].open) sprintf(status_buf + strlen(status_buf), "midi%d: <%s> at 0x%x irq %d dma %d:%d\n", i, midi_info[i].name, midi_info[i].io_base, midi_info[i].irq, midi_info[i].dbuf_out.chan, midi_info[i].dbuf_in.chan); if (pcm_info[i].synth_base) { char *s = "???"; switch (pcm_info[i].synth_type) { case 2 : s = "OPL2"; break; case 3 : s = "OPL3"; break; case 4 : s = "OPL4"; break; } sprintf(status_buf + strlen(status_buf), "sequencer%d: <%s> at 0x%x (not functional)\n", i, s, pcm_info[i].synth_base); } } status_len = strlen(status_buf) ;}/* * finally, some "libraries" *//* * isa_dmastatus1() is a wrapper for isa_dmastatus(), which * might return -1 or -2 in some cases (errors). Since for the * user code it is more comfortable not to check for these cases, * negative values are mapped back to 0 (which is reasonable). */intisa_dmastatus1(int channel){ int r = isa_dmastatus(channel); if (r<0) r = 0; return r;}/* * snd_conflict scans already-attached boards to see if * the current address is conflicting with one of the already * assigned ones. Returns 1 if a conflict is detected. */intsnd_conflict(int io_base){ int i; for (i=0; i< NPCM_MAX ; i++) { if ( (io_base == pcm_info[i].io_base ) || (io_base == pcm_info[i].alt_base ) || (io_base == pcm_info[i].conf_base) || (io_base == pcm_info[i].mix_base ) || (io_base == pcm_info[i].midi_base) || (io_base == pcm_info[i].synth_base) ) { BVDDB(printf("device at 0x%x already attached as unit %d\n", io_base, i);) return 1 ; } } return 0;}voidsnd_set_blocksize(snddev_info *d){ int tmp ; /* * compute the sample size, and possibly * set the blocksize so as to guarantee approx 1/4s * between callbacks. */ tmp = 1 ; if (d->flags & SND_F_STEREO) tmp += tmp; if (d->play_fmt & (AFMT_S16_LE|AFMT_U16_LE)) tmp += tmp; d->dbuf_out.sample_size = tmp ; tmp = tmp * d->play_speed; if ( (d->flags & SND_F_HAS_SIZE) == 0) { d->play_blocksize = (tmp / 4) & ~3; /* 0.25s, aligned to 4 */ RANGE (d->play_blocksize, 1024, (d->bufsize / 4) & ~3); } tmp = 1 ; if (d->flags & SND_F_STEREO) tmp += tmp; if (d->rec_fmt & (AFMT_S16_LE|AFMT_U16_LE)) tmp += tmp; tmp = tmp * d->rec_speed; d->dbuf_in.sample_size = tmp ; if ( (d->flags & SND_F_HAS_SIZE) == 0) { d->rec_blocksize = (tmp / 4) & ~3; /* 0.25s, aligned to 4 */ RANGE (d->rec_blocksize, 1024, (d->bufsize / 4) & ~3); }}/* * The various mixers use a variety of bitmasks etc. The Voxware * driver had a very nice technique to describe a mixer and interface * to it. A table defines, for each channel, which register, bits, * offset, polarity to use. This procedure creates the new value * using the table and the old value. */voidchange_bits(mixer_tab *t, u_char *regval, int dev, int chn, int newval){ u_char mask; int shift; DEB(printf("ch_bits dev %d ch %d val %d old 0x%02x " "r %d p %d bit %d off %d\n", dev, chn, newval, *regval, (*t)[dev][chn].regno, (*t)[dev][chn].polarity, (*t)[dev][chn].nbits, (*t)[dev][chn].bitoffs ) ); if ( (*t)[dev][chn].polarity == 1) /* reverse */ newval = 100 - newval ; mask = (1 << (*t)[dev][chn].nbits) - 1; newval = (int) ((newval * mask) + 50) / 100; /* Scale it */ shift = (*t)[dev][chn].bitoffs /*- (*t)[dev][LEFT_CHN].nbits + 1*/; *regval &= ~(mask << shift); /* Filter out the previous value */ *regval |= (newval & mask) << shift; /* Set the new value */}/* * code for translating between U8 and ULAW. Needed to support * /dev/audio on the SoundBlaster. Actually, we would also need * ulaw -> 16 bits (for the soundblaster as well, when used in * full-duplex) */#if 1voidtranslate_bytes (u_char *table, u_char *buff, int n){ u_long i; if (n <= 0) return; for (i = 0; i < n; ++i) buff[i] = table[buff[i]];}#else/* inline */voidtranslate_bytes (const void *table, void *buff, int n){ if (n > 0) { __asm__ ( " cld\n" "1: lodsb\n" " xlatb\n" " stosb\n" " loop 1b\n": : "b" ((long) table), "c" (n), "D" ((long) buff), "S" ((long) buff) : "bx", "cx", "di", "si", "ax"); }} #endif#endif /* NPCM > 0 */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -