version 1.2, 2008/08/14 15:25:16 |
version 1.3, 2008/10/26 08:49:43 |
|
|
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <unistd.h> |
#include <unistd.h> |
#include <signal.h> |
|
#include <err.h> |
|
|
|
#include "dev.h" |
#include "dev.h" |
#include "abuf.h" |
#include "abuf.h" |
#include "aproc.h" |
#include "aproc.h" |
#include "file.h" |
#include "pipe.h" |
#include "conf.h" |
#include "conf.h" |
|
#include "safile.h" |
|
|
int quit_flag, pause_flag; |
unsigned dev_bufsz, dev_round, dev_rate; |
unsigned dev_infr, dev_onfr; |
unsigned dev_rate_div, dev_round_div; |
struct aparams dev_ipar, dev_opar; |
struct aparams dev_ipar, dev_opar; |
struct aproc *dev_mix, *dev_sub, *dev_rec, *dev_play; |
struct aproc *dev_mix, *dev_sub, *dev_rec, *dev_play; |
struct file *dev_file; |
struct file *dev_file; |
struct devops *devops = &devops_sun; |
|
|
|
/* |
/* |
* SIGINT handler, it raises the quit flag. If the flag is already set, |
* supported rates |
* that means that the last SIGINT was not handled, because the process |
|
* is blocked somewhere, so exit |
|
*/ |
*/ |
void |
#define NRATES (sizeof(dev_rates) / sizeof(dev_rates[0])) |
sigint(int s) |
unsigned dev_rates[] = { |
{ |
6400, 7200, 8000, 9600, 11025, 12000, |
if (quit_flag) |
12800, 14400, 16000, 19200, 22050, 24000, |
_exit(1); |
25600, 28800, 32000, 38400, 44100, 48000, |
quit_flag = 1; |
51200, 57600, 64000, 76800, 88200, 96000, |
} |
102400, 115200, 128000, 153600, 176400, 192000 |
|
}; |
|
|
/* |
/* |
* called when the user hits ctrl-z |
* factors of supported rates |
*/ |
*/ |
void |
#define NPRIMES (sizeof(dev_primes) / sizeof(dev_primes[0])) |
sigtstp(int s) |
unsigned dev_primes[] = {2, 3, 5, 7}; |
{ |
|
pause_flag = 1; |
|
} |
|
|
|
/* |
int |
* SIGCONT is send when resumed after SIGTSTP or SIGSTOP. If the pause |
dev_setrate(unsigned rate) |
* flag is not set, that means that the process was not suspended by |
|
* dev_suspend(), which means that we lost the sync; since we cannot |
|
* resync, just exit |
|
*/ |
|
void |
|
sigcont(int s) |
|
{ |
{ |
static char msg[] = "can't resume afer SIGSTOP, terminating...\n"; |
unsigned i, r, p; |
|
|
if (!pause_flag) { |
|
write(STDERR_FILENO, msg, sizeof(msg) - 1); |
|
_exit(1); |
|
} |
|
} |
|
|
|
/* |
r = 1000 * rate; |
* suicide with SIGTSTP (tty stop) as if the user had hit ctrl-z |
for (i = 0; i < NRATES; i++) { |
*/ |
if (i == NRATES) { |
void |
fprintf(stderr, "dev_setrate: %u, unsupported\n", rate); |
dev_suspend(void) |
return 0; |
{ |
} |
struct sigaction sa; |
if (r > 996 * dev_rates[i] && |
|
r < 1004 * dev_rates[i]) { |
sigfillset(&sa.sa_mask); |
dev_rate = dev_rates[i]; |
sa.sa_flags = SA_RESTART; |
|
sa.sa_handler = SIG_DFL; |
|
if (sigaction(SIGTSTP, &sa, NULL) < 0) |
|
err(1, "sigaction"); |
|
DPRINTF("suspended by tty\n"); |
|
kill(getpid(), SIGTSTP); |
|
pause_flag = 0; |
|
sa.sa_handler = sigtstp; |
|
if (sigaction(SIGTSTP, &sa, NULL) < 0) |
|
err(1, "sigaction"); |
|
DPRINTF("resumed after suspend\n"); |
|
} |
|
|
|
/* |
|
* fill playback buffer, so when device is started there |
|
* are samples to play |
|
*/ |
|
void |
|
dev_fill(void) |
|
{ |
|
struct abuf *buf; |
|
|
|
|
|
/* |
|
* if there are no inputs, zero fill the mixer |
|
*/ |
|
if (dev_mix && LIST_EMPTY(&dev_mix->ibuflist)) |
|
mix_pushzero(dev_mix); |
|
DPRINTF("filling play buffers...\n"); |
|
for (;;) { |
|
if (!dev_file->wproc) { |
|
DPRINTF("fill: no writer\n"); |
|
break; |
break; |
} |
} |
if (dev_file->events & POLLOUT) { |
} |
/* |
|
* kernel buffers are full, but continue |
dev_rate_div = dev_rate; |
* until the play buffer is full too. |
dev_round_div = dev_round; |
*/ |
for (i = 0; i < NPRIMES; i++) { |
buf = LIST_FIRST(&dev_file->wproc->ibuflist); |
p = dev_primes[i]; |
if (!ABUF_WOK(buf)) |
while (dev_rate_div % p == 0 && dev_round_div % p == 0) { |
break; /* buffer full */ |
dev_rate_div /= p; |
if (!buf->wproc) |
dev_round_div /= p; |
break; /* will never be filled */ |
|
} |
} |
if (!file_poll()) |
|
break; |
|
if (pause_flag) |
|
dev_suspend(); |
|
} |
} |
|
return 1; |
} |
} |
|
|
/* |
|
* flush recorded samples once the device is stopped so |
|
* they aren't lost |
|
*/ |
|
void |
void |
dev_flush(void) |
dev_roundrate(unsigned *newrate, unsigned *newround) |
{ |
{ |
struct abuf *buf; |
*newrate += dev_rate_div - 1; |
|
*newrate -= *newrate % dev_rate_div; |
DPRINTF("flushing record buffers...\n"); |
*newround = *newrate * dev_round_div / dev_rate_div; |
for (;;) { |
|
if (!dev_file->rproc) { |
|
DPRINTF("flush: no more reader\n"); |
|
break; |
|
} |
|
if (dev_file->events & POLLIN) { |
|
/* |
|
* we drained kernel buffers, but continue |
|
* until the record buffer is empty. |
|
*/ |
|
buf = LIST_FIRST(&dev_file->rproc->obuflist); |
|
if (!ABUF_ROK(buf)) |
|
break; /* buffer empty */ |
|
if (!buf->rproc) |
|
break; /* will never be drained */ |
|
} |
|
if (!file_poll()) |
|
break; |
|
if (pause_flag) |
|
dev_suspend(); |
|
} |
|
} |
} |
|
|
|
|
/* |
/* |
* open the device with the given hardware parameters and create a mixer |
* open the device with the given hardware parameters and create a mixer |
* and a multiplexer connected to it with all necessary conversions |
* and a multiplexer connected to it with all necessary conversions |
* setup |
* setup |
*/ |
*/ |
void |
void |
dev_init(char *devpath, struct aparams *dipar, struct aparams *dopar) |
dev_init(char *devpath, |
|
struct aparams *dipar, struct aparams *dopar, unsigned bufsz) |
{ |
{ |
int fd; |
|
struct sigaction sa; |
|
unsigned infr, onfr; |
|
struct aparams ipar, opar; |
struct aparams ipar, opar; |
struct aproc *conv; |
struct aproc *conv; |
struct abuf *buf; |
struct abuf *buf; |
|
unsigned nfr, ibufsz, obufsz; |
|
|
quit_flag = 0; |
/* |
pause_flag = 0; |
* use 1/4 of the total buffer for the device |
|
*/ |
sigfillset(&sa.sa_mask); |
dev_bufsz = (bufsz + 3) / 4; |
sa.sa_flags = SA_RESTART; |
dev_file = (struct file *)safile_new(&safile_ops, devpath, |
sa.sa_handler = sigint; |
dipar, dopar, &dev_bufsz, &dev_round); |
if (sigaction(SIGINT, &sa, NULL) < 0) |
if (!dev_file) |
err(1, "sigaction"); |
|
sa.sa_handler = sigtstp; |
|
if (sigaction(SIGTSTP, &sa, NULL) < 0) |
|
err(1, "sigaction"); |
|
sa.sa_handler = sigcont; |
|
if (sigaction(SIGCONT, &sa, NULL) < 0) |
|
err(1, "sigaction"); |
|
|
|
fd = devops->open(devpath, dipar, dopar, &infr, &onfr); |
|
if (fd < 0) |
|
exit(1); |
exit(1); |
dev_file = file_new(fd, devpath); |
if (!dev_setrate(dipar ? dipar->rate : dopar->rate)) |
|
exit(1); |
|
if (dipar) { |
|
dipar->rate = dev_rate; |
|
if (debug_level > 0) { |
|
DPRINTF("dev_init: dipar: "); |
|
aparams_print(dipar); |
|
DPRINTF("\n"); |
|
} |
|
} |
|
if (dopar) { |
|
dopar->rate = dev_rate; |
|
if (debug_level > 0) { |
|
DPRINTF("dev_init: dopar: "); |
|
aparams_print(dopar); |
|
DPRINTF("\n"); |
|
} |
|
} |
|
nfr = ibufsz = obufsz = dev_bufsz; |
|
|
/* |
/* |
* create record chain |
* create record chain: use 1/4 for the file i/o buffers |
*/ |
*/ |
if (dipar) { |
if (dipar) { |
aparams_init(&ipar, dipar->cmin, dipar->cmax, dipar->rate); |
aparams_init(&ipar, dipar->cmin, dipar->cmax, dipar->rate); |
infr *= DEFAULT_NBLK; |
|
|
|
/* |
/* |
* create the read end |
* create the read end |
*/ |
*/ |
dev_rec = rpipe_new(dev_file); |
dev_rec = rpipe_new(dev_file); |
buf = abuf_new(infr, aparams_bpf(dipar)); |
buf = abuf_new(nfr, aparams_bpf(dipar)); |
aproc_setout(dev_rec, buf); |
aproc_setout(dev_rec, buf); |
|
ibufsz += nfr; |
|
|
/* |
/* |
* append a converter, if needed |
* append a converter, if needed |
|
|
} |
} |
conv = conv_new("subconv", dipar, &ipar); |
conv = conv_new("subconv", dipar, &ipar); |
aproc_setin(conv, buf); |
aproc_setin(conv, buf); |
buf = abuf_new(infr, aparams_bpf(&ipar)); |
buf = abuf_new(nfr, aparams_bpf(&ipar)); |
aproc_setout(conv, buf); |
aproc_setout(conv, buf); |
|
ibufsz += nfr; |
} |
} |
dev_ipar = ipar; |
dev_ipar = ipar; |
dev_infr = infr; |
|
|
|
/* |
/* |
* append a "sub" to which clients will connect |
* append a "sub" to which clients will connect |
*/ |
*/ |
dev_sub = sub_new(); |
dev_sub = sub_new("sub", nfr); |
aproc_setin(dev_sub, buf); |
aproc_setin(dev_sub, buf); |
} else { |
} else { |
dev_rec = NULL; |
dev_rec = NULL; |
|
|
*/ |
*/ |
if (dopar) { |
if (dopar) { |
aparams_init(&opar, dopar->cmin, dopar->cmax, dopar->rate); |
aparams_init(&opar, dopar->cmin, dopar->cmax, dopar->rate); |
onfr *= DEFAULT_NBLK; |
|
|
|
/* |
/* |
* create the write end |
* create the write end |
*/ |
*/ |
dev_play = wpipe_new(dev_file); |
dev_play = wpipe_new(dev_file); |
buf = abuf_new(onfr, aparams_bpf(dopar)); |
buf = abuf_new(nfr, aparams_bpf(dopar)); |
aproc_setin(dev_play, buf); |
aproc_setin(dev_play, buf); |
|
obufsz += nfr; |
|
|
/* |
/* |
* append a converter, if needed |
* append a converter, if needed |
*/ |
*/ |
|
|
} |
} |
conv = conv_new("mixconv", &opar, dopar); |
conv = conv_new("mixconv", &opar, dopar); |
aproc_setout(conv, buf); |
aproc_setout(conv, buf); |
buf = abuf_new(onfr, aparams_bpf(&opar)); |
buf = abuf_new(nfr, aparams_bpf(&opar)); |
aproc_setin(conv, buf); |
aproc_setin(conv, buf); |
*dopar = opar; |
obufsz += nfr; |
} |
} |
dev_opar = opar; |
dev_opar = opar; |
dev_onfr = onfr; |
|
|
|
/* |
/* |
* append a "mix" to which clients will connect |
* append a "mix" to which clients will connect |
*/ |
*/ |
dev_mix = mix_new(); |
dev_mix = mix_new("mix", nfr); |
aproc_setout(dev_mix, buf); |
aproc_setout(dev_mix, buf); |
} else { |
} else { |
dev_play = NULL; |
dev_play = NULL; |
dev_mix = NULL; |
dev_mix = NULL; |
} |
} |
|
dev_bufsz = (dopar) ? obufsz : ibufsz; |
|
DPRINTF("dev_init: using %u fpb\n", dev_bufsz); |
|
dev_start(); |
} |
} |
|
|
/* |
/* |
|
|
void |
void |
dev_done(void) |
dev_done(void) |
{ |
{ |
struct sigaction sa; |
|
struct file *f; |
struct file *f; |
|
|
/* |
if (dev_mix) { |
* generate EOF on all inputs (including device), so once |
/* |
* buffers are drained, everything will be cleaned |
* generate EOF on all inputs (but not the device), and |
*/ |
* put the mixer in ``autoquit'' state, so once buffers |
LIST_FOREACH(f, &file_list, entry) { |
* are drained the mixer will terminate and shutdown the |
if (f->rproc) |
* write-end of the device |
file_eof(f); |
* |
|
* NOTE: since file_eof() can destroy the file and |
|
* reorder the file_list, we have to restart the loop |
|
* after each call to file_eof() |
|
*/ |
|
restart: |
|
LIST_FOREACH(f, &file_list, entry) { |
|
if (f != dev_file && f->rproc) { |
|
file_eof(f); |
|
goto restart; |
|
} |
|
} |
|
if (dev_mix) |
|
dev_mix->u.mix.flags |= MIX_AUTOQUIT; |
|
|
|
/* |
|
* wait play chain to terminate |
|
*/ |
|
while (dev_file->wproc != NULL) { |
|
if (!file_poll()) |
|
break; |
|
} |
|
dev_mix = 0; |
} |
} |
/* |
if (dev_sub) { |
* destroy automatically mixe instead |
/* |
* of generating silence |
* same as above, but for the record chain: generate eof |
*/ |
* on the read-end of the device and wait record buffers |
if (dev_mix) |
* to desappear. We must stop the device first, because |
dev_mix->u.mix.flags |= MIX_AUTOQUIT; |
* play-end will underrun (and xrun correction code will |
if (dev_sub) |
* insert silence on the record-end of the device) |
dev_sub->u.sub.flags |= SUB_AUTOQUIT; |
*/ |
/* |
dev_stop(); |
* drain buffers of terminated inputs. |
file_eof(dev_file); |
*/ |
if (dev_sub) |
for (;;) { |
dev_sub->u.sub.flags |= SUB_AUTOQUIT; |
if (!file_poll()) |
for (;;) { |
break; |
if (!file_poll()) |
|
break; |
|
} |
|
dev_sub = NULL; |
} |
} |
devops->close(dev_file->fd); |
|
|
|
sigfillset(&sa.sa_mask); |
|
sa.sa_flags = SA_RESTART; |
|
sa.sa_handler = SIG_DFL; |
|
if (sigaction(SIGINT, &sa, NULL) < 0) |
|
err(1, "sigaction"); |
|
if (sigaction(SIGTSTP, &sa, NULL) < 0) |
|
err(1, "sigaction"); |
|
if (sigaction(SIGCONT, &sa, NULL) < 0) |
|
err(1, "sigaction"); |
|
} |
} |
|
|
/* |
/* |
|
|
void |
void |
dev_start(void) |
dev_start(void) |
{ |
{ |
dev_fill(); |
|
if (dev_mix) |
if (dev_mix) |
dev_mix->u.mix.flags |= MIX_DROP; |
dev_mix->u.mix.flags |= MIX_DROP; |
if (dev_sub) |
if (dev_sub) |
dev_sub->u.sub.flags |= SUB_DROP; |
dev_sub->u.sub.flags |= SUB_DROP; |
devops->start(dev_file->fd); |
dev_file->ops->start(dev_file); |
} |
} |
|
|
/* |
/* |
|
|
void |
void |
dev_stop(void) |
dev_stop(void) |
{ |
{ |
devops->stop(dev_file->fd); |
dev_file->ops->stop(dev_file); |
if (dev_mix) |
if (dev_mix) |
dev_mix->u.mix.flags &= ~MIX_DROP; |
dev_mix->u.mix.flags &= ~MIX_DROP; |
if (dev_sub) |
if (dev_sub) |
dev_sub->u.sub.flags &= ~SUB_DROP; |
dev_sub->u.sub.flags &= ~SUB_DROP; |
dev_flush(); |
|
} |
} |
|
|
/* |
/* |
* loop until there's either input or output to process |
* sync play buffer to rec buffer (for instance when one of |
|
* them underruns/overruns) |
*/ |
*/ |
void |
void |
dev_run(int autoquit) |
dev_sync(struct abuf *ibuf, struct abuf *obuf) |
{ |
{ |
while (!quit_flag) { |
struct abuf *pbuf, *rbuf; |
if ((!dev_mix || LIST_EMPTY(&dev_mix->ibuflist)) && |
int delta; |
(!dev_sub || LIST_EMPTY(&dev_sub->obuflist)) && autoquit) |
|
|
if (!dev_mix || !dev_sub) |
|
return; |
|
pbuf = LIST_FIRST(&dev_mix->obuflist); |
|
if (!pbuf) |
|
return; |
|
rbuf = LIST_FIRST(&dev_sub->ibuflist); |
|
if (!rbuf) |
|
return; |
|
for (;;) { |
|
if (!ibuf || !ibuf->rproc) { |
|
DPRINTF("dev_sync: reader desappeared\n"); |
|
return; |
|
} |
|
if (ibuf->rproc == dev_mix) |
break; |
break; |
if (!file_poll()) |
ibuf = LIST_FIRST(&ibuf->rproc->obuflist); |
break; |
} |
if (pause_flag) { |
for (;;) { |
devops->stop(dev_file->fd); |
if (!obuf || !obuf->wproc) { |
dev_flush(); |
DPRINTF("dev_sync: writer desappeared\n"); |
dev_suspend(); |
return; |
dev_fill(); |
|
devops->start(dev_file->fd); |
|
} |
} |
|
if (obuf->wproc == dev_sub) |
|
break; |
|
obuf = LIST_FIRST(&obuf->wproc->ibuflist); |
} |
} |
|
|
|
/* |
|
* calculate delta, the number of frames the play chain is ahead |
|
* of the record chain. It's necessary to schedule silences (or |
|
* drops) in order to start playback and record in sync. |
|
*/ |
|
delta = |
|
rbuf->bpf * (pbuf->abspos + pbuf->used) - |
|
pbuf->bpf * rbuf->abspos; |
|
delta /= pbuf->bpf * rbuf->bpf; |
|
DPRINTF("dev_sync: delta = %d, ppos = %u, pused = %u, rpos = %u\n", |
|
delta, pbuf->abspos, pbuf->used, rbuf->abspos); |
|
|
|
if (delta > 0) { |
|
/* |
|
* if the play chain is ahead (most cases) drop some of |
|
* the recorded input, to get both in sync |
|
*/ |
|
obuf->drop += delta * obuf->bpf; |
|
abuf_ipos(obuf, -delta); |
|
} else if (delta < 0) { |
|
/* |
|
* if record chain is ahead (should never happen, |
|
* right?) then insert silence to play |
|
*/ |
|
ibuf->silence += -delta * ibuf->bpf; |
|
abuf_opos(ibuf, delta); |
|
} else |
|
DPRINTF("dev_sync: nothing to do\n"); |
} |
} |
|
|
/* |
/* |
|
|
struct abuf *ibuf, struct aparams *ipar, unsigned underrun, |
struct abuf *ibuf, struct aparams *ipar, unsigned underrun, |
struct abuf *obuf, struct aparams *opar, unsigned overrun) |
struct abuf *obuf, struct aparams *opar, unsigned overrun) |
{ |
{ |
int delta; |
|
struct abuf *pbuf = NULL, *rbuf = NULL; |
struct abuf *pbuf = NULL, *rbuf = NULL; |
struct aproc *conv; |
struct aproc *conv; |
|
unsigned nfr; |
|
|
if (ibuf) { |
if (ibuf) { |
pbuf = LIST_FIRST(&dev_mix->obuflist); |
pbuf = LIST_FIRST(&dev_mix->obuflist); |
|
|
aparams_print2(ipar, &dev_opar); |
aparams_print2(ipar, &dev_opar); |
fprintf(stderr, "\n"); |
fprintf(stderr, "\n"); |
} |
} |
|
nfr = (dev_bufsz + 3) / 4 + dev_round - 1; |
|
nfr -= nfr % dev_round; |
conv = conv_new(name, ipar, &dev_opar); |
conv = conv_new(name, ipar, &dev_opar); |
aproc_setin(conv, ibuf); |
aproc_setin(conv, ibuf); |
ibuf = abuf_new(dev_onfr, aparams_bpf(&dev_opar)); |
ibuf = abuf_new(nfr, aparams_bpf(&dev_opar)); |
aproc_setout(conv, ibuf); |
aproc_setout(conv, ibuf); |
|
/* XXX: call abuf_fill() here ? */ |
} |
} |
aproc_setin(dev_mix, ibuf); |
aproc_setin(dev_mix, ibuf); |
|
abuf_opos(ibuf, -dev_mix->u.mix.lat); |
ibuf->xrun = underrun; |
ibuf->xrun = underrun; |
mix_setmaster(dev_mix); |
|
} |
} |
if (obuf) { |
if (obuf) { |
rbuf = LIST_FIRST(&dev_sub->ibuflist); |
rbuf = LIST_FIRST(&dev_sub->ibuflist); |
|
|
aparams_print2(&dev_ipar, opar); |
aparams_print2(&dev_ipar, opar); |
fprintf(stderr, "\n"); |
fprintf(stderr, "\n"); |
} |
} |
|
nfr = (dev_bufsz + 3) / 4 + dev_round - 1; |
|
nfr -= nfr % dev_round; |
conv = conv_new(name, &dev_ipar, opar); |
conv = conv_new(name, &dev_ipar, opar); |
aproc_setout(conv, obuf); |
aproc_setout(conv, obuf); |
obuf = abuf_new(dev_infr, aparams_bpf(&dev_ipar)); |
obuf = abuf_new(nfr, aparams_bpf(&dev_ipar)); |
aproc_setin(conv, obuf); |
aproc_setin(conv, obuf); |
} |
} |
aproc_setout(dev_sub, obuf); |
aproc_setout(dev_sub, obuf); |
|
abuf_ipos(obuf, -dev_sub->u.sub.lat); |
obuf->xrun = overrun; |
obuf->xrun = overrun; |
} |
} |
|
|
/* |
/* |
* calculate delta, the number of frames the play chain is ahead |
* sync play to record |
* of the record chain. It's necessary to schedule silences (or |
|
* drops) in order to start playback and record in sync. |
|
*/ |
*/ |
if (ibuf && obuf) { |
if (ibuf && obuf) { |
delta = |
ibuf->duplex = obuf; |
rbuf->bpf * (pbuf->abspos + pbuf->used) - |
obuf->duplex = ibuf; |
pbuf->bpf * rbuf->abspos; |
dev_sync(ibuf, obuf); |
delta /= pbuf->bpf * rbuf->bpf; |
|
DPRINTF("dev_attach: ppos = %u, pused = %u, rpos = %u\n", |
|
pbuf->abspos, pbuf->used, rbuf->abspos); |
|
} else |
|
delta = 0; |
|
DPRINTF("dev_attach: delta = %u\n", delta); |
|
|
|
if (delta > 0) { |
|
/* |
|
* if the play chain is ahead (most cases) drop some of |
|
* the recorded input, to get both in sync |
|
*/ |
|
obuf->drop += delta * obuf->bpf; |
|
} else if (delta < 0) { |
|
/* |
|
* if record chain is ahead (should never happen, |
|
* right?) then insert silence to play |
|
*/ |
|
ibuf->silence += -delta * ibuf->bpf; |
|
} |
|
if (ibuf && (dev_mix->u.mix.flags & MIX_DROP)) { |
|
/* |
|
* fill the play buffer with silence to avoid underruns, |
|
* drop samples on the input to keep play/record in sync |
|
* after the silence insertion |
|
*/ |
|
ibuf->silence += dev_onfr * ibuf->bpf; |
|
if (obuf) |
|
obuf->drop += dev_onfr * obuf->bpf; |
|
/* |
|
* force data to propagate |
|
*/ |
|
abuf_run(ibuf); |
|
DPRINTF("dev_attach: ibuf: used = %u, silence = %u\n", |
|
ibuf->used, ibuf->silence); |
|
} |
|
if (obuf && (dev_sub->u.mix.flags & SUB_DROP)) { |
|
abuf_run(obuf); |
|
DPRINTF("dev_attach: ibuf: used = %u, drop = %u\n", |
|
obuf->used, obuf->drop); |
|
} |
} |
} |
} |