diff options
Diffstat (limited to 'sys/dev/sound/usb')
-rw-r--r-- | sys/dev/sound/usb/uaudio.c | 390 | ||||
-rw-r--r-- | sys/dev/sound/usb/uaudio.h | 4 | ||||
-rw-r--r-- | sys/dev/sound/usb/uaudio_pcm.c | 14 |
3 files changed, 277 insertions, 131 deletions
diff --git a/sys/dev/sound/usb/uaudio.c b/sys/dev/sound/usb/uaudio.c index 5f11bba..d99002e 100644 --- a/sys/dev/sound/usb/uaudio.c +++ b/sys/dev/sound/usb/uaudio.c @@ -212,26 +212,25 @@ struct uaudio_chan { uint32_t sample_rem; uint32_t sample_curr; uint32_t max_buf; + int32_t jitter_rem; + int32_t jitter_curr; + + int feedback_rate; uint32_t pcm_format[2]; uint16_t bytes_per_frame[2]; - uint8_t num_alt; - uint8_t cur_alt; - uint8_t set_alt; - uint8_t operation; + uint32_t intr_counter; + uint32_t running; + uint32_t num_alt; + uint32_t cur_alt; + uint32_t set_alt; + uint32_t operation; #define CHAN_OP_NONE 0 #define CHAN_OP_START 1 #define CHAN_OP_STOP 2 #define CHAN_OP_DRAIN 3 - - /* USB audio feedback endpoint state */ - struct { - uint16_t time; /* I/O interrupt count */ - int16_t constant; /* sample rate adjustment in Hz */ - int16_t remainder; /* current remainder */ - } feedback; }; #define UMIDI_EMB_JACK_MAX 16 /* units */ @@ -1090,6 +1089,11 @@ uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_clas uaudio_mixer_register_sysctl(sc, dev); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "feedback_rate", CTLFLAG_RD, &sc->sc_play_chan.feedback_rate, + 0, "Feedback sample rate in Hz"); + return (0); /* success */ detach: @@ -1288,7 +1292,6 @@ uaudio_configure_msg_sub(struct uaudio_softc *sc, chan->frames_per_second = fps; chan->sample_rem = chan_alt->sample_rate % fps; chan->sample_curr = 0; - chan->frames_per_second = fps; /* compute required buffer size */ buf_size = (chan->bytes_per_frame[1] * frames); @@ -1968,7 +1971,7 @@ uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct usb_page_cache *pc; - uint64_t sample_rate = ch->usb_alt[ch->cur_alt].sample_rate; + uint64_t sample_rate; uint8_t buf[4]; uint64_t temp; int len; @@ -2011,6 +2014,8 @@ uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error) temp *= 125ULL; + sample_rate = ch->usb_alt[ch->cur_alt].sample_rate; + /* auto adjust */ while (temp < (sample_rate - (sample_rate / 4))) temp *= 2; @@ -2018,35 +2023,10 @@ uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error) while (temp > (sample_rate + (sample_rate / 2))) temp /= 2; - /* - * Some USB audio devices only report a sample rate - * different from the nominal one when they want one - * more or less sample. Make sure we catch this case - * by pulling the sample rate offset slowly towards - * zero if the reported value is equal to the sample - * rate. - */ - if (temp > sample_rate) - ch->feedback.constant += 1; - else if (temp < sample_rate) - ch->feedback.constant -= 1; - else if (ch->feedback.constant > 0) - ch->feedback.constant--; - else if (ch->feedback.constant < 0) - ch->feedback.constant++; - - DPRINTF("Comparing %d Hz :: %d Hz :: %d samples drift\n", - (int)temp, (int)sample_rate, (int)ch->feedback.constant); + DPRINTF("Comparing %d Hz :: %d Hz\n", + (int)temp, (int)sample_rate); - /* - * Range check sync constant. We cannot change the - * number of samples per second by more than the value - * defined by "UAUDIO_IRQS": - */ - if (ch->feedback.constant > UAUDIO_IRQS) - ch->feedback.constant = UAUDIO_IRQS; - else if (ch->feedback.constant < -UAUDIO_IRQS) - ch->feedback.constant = -UAUDIO_IRQS; + ch->feedback_rate = temp; break; case USB_ST_SETUP: @@ -2060,43 +2040,98 @@ uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error) } } +static int +uaudio_chan_is_async(struct uaudio_chan *ch, uint8_t alt) +{ + uint8_t attr = ch->usb_alt[alt].p_ed1->bmAttributes; + return (UE_GET_ISO_TYPE(attr) == UE_ISO_ASYNC); +} + static void uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); + struct uaudio_chan *ch_rec; struct usb_page_cache *pc; - uint32_t sample_size = ch->usb_alt[ch->cur_alt].sample_size; uint32_t mfl; uint32_t total; uint32_t blockcount; uint32_t n; uint32_t offset; + int sample_size; int actlen; int sumlen; - usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); - - if (ch->end == ch->start) { - DPRINTF("no buffer!\n"); + if (ch->running == 0 || ch->start == ch->end) { + DPRINTF("not running or no buffer!\n"); return; } + /* check if there is a record channel */ + if (ch->priv_sc->sc_rec_chan.num_alt > 0) + ch_rec = &ch->priv_sc->sc_rec_chan; + else + ch_rec = NULL; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: +tr_setup: + if (ch_rec != NULL) { + /* reset receive jitter counters */ + mtx_lock(ch_rec->pcm_mtx); + ch_rec->jitter_curr = 0; + ch_rec->jitter_rem = 0; + mtx_unlock(ch_rec->pcm_mtx); + } + + /* reset transmit jitter counters */ + ch->jitter_curr = 0; + ch->jitter_rem = 0; + + /* FALLTHROUGH */ case USB_ST_TRANSFERRED: -tr_transferred: if (actlen < sumlen) { DPRINTF("short transfer, " "%d of %d bytes\n", actlen, sumlen); } chn_intr(ch->pcm_ch); + /* + * Check for asynchronous playback endpoint and that + * the playback endpoint is properly configured: + */ + if (ch_rec != NULL && + uaudio_chan_is_async(ch, ch->cur_alt) != 0) { + mtx_lock(ch_rec->pcm_mtx); + if (ch_rec->cur_alt < ch_rec->num_alt) { + int64_t tx_jitter; + int64_t rx_rate; + + /* translate receive jitter into transmit jitter */ + tx_jitter = ch->usb_alt[ch->cur_alt].sample_rate; + tx_jitter = (tx_jitter * ch_rec->jitter_curr) + + ch->jitter_rem; + + /* reset receive jitter counters */ + ch_rec->jitter_curr = 0; + ch_rec->jitter_rem = 0; + + /* compute exact number of transmit jitter samples */ + rx_rate = ch_rec->usb_alt[ch_rec->cur_alt].sample_rate; + ch->jitter_curr += tx_jitter / rx_rate; + ch->jitter_rem = tx_jitter % rx_rate; + } + mtx_unlock(ch_rec->pcm_mtx); + } + /* start the SYNC transfer one time per second, if any */ - if (++(ch->feedback.time) >= UAUDIO_IRQS) { - ch->feedback.time = 0; + if (++(ch->intr_counter) >= UAUDIO_IRQS) { + ch->intr_counter = 0; usbd_transfer_start(ch->xfer[UAUDIO_NCHANBUFS]); } - case USB_ST_SETUP: mfl = usbd_xfer_max_framelen(xfer); if (ch->bytes_per_frame[1] > mfl) { @@ -2112,6 +2147,9 @@ tr_transferred: /* setup number of frames */ usbd_xfer_set_frames(xfer, blockcount); + /* get sample size */ + sample_size = ch->usb_alt[ch->cur_alt].sample_size; + /* reset total length */ total = 0; @@ -2127,31 +2165,23 @@ tr_transferred: frame_len = ch->bytes_per_frame[0]; } - if (n == (blockcount - 1)) { - /* - * Update sync remainder and check if - * we should transmit more or less - * data: - */ - ch->feedback.remainder += ch->feedback.constant; - if (ch->feedback.remainder >= UAUDIO_IRQS) { - ch->feedback.remainder -= UAUDIO_IRQS; - DPRINTFN(6, "sending one sample more\n"); - if ((frame_len + sample_size) <= mfl) - frame_len += sample_size; - } else if (ch->feedback.remainder <= -UAUDIO_IRQS) { - ch->feedback.remainder += UAUDIO_IRQS; - DPRINTFN(6, "sending one sample less\n"); - if (frame_len >= sample_size) - frame_len -= sample_size; - } + /* handle free running clock case */ + if (ch->jitter_curr > 0 && + (frame_len + sample_size) <= mfl) { + DPRINTFN(6, "sending one sample more\n"); + ch->jitter_curr--; + frame_len += sample_size; + } else if (ch->jitter_curr < 0 && + frame_len >= sample_size) { + DPRINTFN(6, "sending one sample less\n"); + ch->jitter_curr++; + frame_len -= sample_size; } - usbd_xfer_set_frame_len(xfer, n, frame_len); total += frame_len; } - DPRINTFN(6, "transfer %d bytes\n", total); + DPRINTFN(6, "transferring %d bytes\n", total); offset = 0; @@ -2159,28 +2189,25 @@ tr_transferred: while (total > 0) { n = (ch->end - ch->cur); - if (n > total) { + if (n > total) n = total; - } + usbd_copy_in(pc, offset, ch->cur, n); total -= n; ch->cur += n; offset += n; - if (ch->cur >= ch->end) { + if (ch->cur >= ch->end) ch->cur = ch->start; - } } - usbd_transfer_submit(xfer); break; default: /* Error */ - if (error == USB_ERR_CANCELLED) { - break; - } - goto tr_transferred; + if (error != USB_ERR_CANCELLED) + goto tr_setup; + break; } } @@ -2196,36 +2223,59 @@ uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error) struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t offset0; - uint32_t offset1; uint32_t mfl; int m; int n; int len; int actlen; int nframes; - int blockcount; - - usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); - mfl = usbd_xfer_max_framelen(xfer); + int expected_bytes; + int sample_size; - if (ch->end == ch->start) { + if (ch->start == ch->end) { DPRINTF("no buffer!\n"); return; } + usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); + mfl = usbd_xfer_max_framelen(xfer); + switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: - DPRINTFN(6, "transferred %d bytes\n", actlen); - offset0 = 0; pc = usbd_xfer_get_frame(xfer, 0); + /* try to compute the number of expected bytes */ + ch->sample_curr += (ch->sample_rem * ch->intr_frames); + + /* compute number of expected bytes */ + expected_bytes = (ch->intr_frames * ch->bytes_per_frame[0]) + + ((ch->sample_curr / ch->frames_per_second) * + (ch->bytes_per_frame[1] - ch->bytes_per_frame[0])); + + /* keep remainder */ + ch->sample_curr %= ch->frames_per_second; + + /* get current sample size */ + sample_size = ch->usb_alt[ch->cur_alt].sample_size; + for (n = 0; n != nframes; n++) { + uint32_t offset1 = offset0; - offset1 = offset0; len = usbd_xfer_frame_len(xfer, n); + /* make sure we only receive complete samples */ + len = len - (len % sample_size); + + /* subtract bytes received from expected payload */ + expected_bytes -= len; + + /* don't receive data when not ready */ + if (ch->running == 0 || ch->cur_alt != ch->set_alt) + continue; + + /* fill ring buffer with samples, if any */ while (len > 0) { m = (ch->end - ch->cur); @@ -2239,33 +2289,46 @@ uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error) offset1 += m; ch->cur += m; - if (ch->cur >= ch->end) { + if (ch->cur >= ch->end) ch->cur = ch->start; - } } offset0 += mfl; } - chn_intr(ch->pcm_ch); + /* update current jitter */ + ch->jitter_curr -= (expected_bytes / sample_size); + + /* don't allow a huge amount of jitter to accumulate */ + nframes = 2 * ch->intr_frames; + + /* range check current jitter */ + if (ch->jitter_curr < -nframes) + ch->jitter_curr = -nframes; + else if (ch->jitter_curr > nframes) + ch->jitter_curr = nframes; + + DPRINTFN(6, "transferred %d bytes, jitter %d samples\n", + actlen, ch->jitter_curr); + + if (ch->running != 0) + chn_intr(ch->pcm_ch); case USB_ST_SETUP: tr_setup: - blockcount = ch->intr_frames; + nframes = ch->intr_frames; - usbd_xfer_set_frames(xfer, blockcount); - for (n = 0; n < blockcount; n++) { + usbd_xfer_set_frames(xfer, nframes); + for (n = 0; n != nframes; n++) usbd_xfer_set_frame_len(xfer, n, mfl); - } usbd_transfer_submit(xfer); break; default: /* Error */ - if (error == USB_ERR_CANCELLED) { - break; - } - goto tr_setup; + if (error != USB_ERR_CANCELLED) + goto tr_setup; + break; } } @@ -2338,13 +2401,7 @@ int uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize) { uint32_t temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt); - sndbuf_setup(ch->pcm_buf, ch->buf, temp); - - ch->start = ch->buf; - ch->end = ch->buf + temp; - ch->cur = ch->buf; - return (temp / 2); } @@ -2358,8 +2415,11 @@ uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize, int uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed) { + struct uaudio_softc *sc; uint8_t x; + sc = ch->priv_sc; + for (x = 0; x < ch->num_alt; x++) { if (ch->usb_alt[x].sample_rate < speed) { /* sample rate is too low */ @@ -2370,7 +2430,9 @@ uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed) if (x != 0) x--; + usb_proc_explore_lock(sc->sc_udev); ch->set_alt = x; + usb_proc_explore_unlock(sc->sc_udev); DPRINTF("Selecting alt %d\n", (int)x); @@ -2441,16 +2503,16 @@ uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format) return (0); } -int -uaudio_chan_start(struct uaudio_chan *ch) +static void +uaudio_chan_start_sub(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; int do_start = 0; - usb_proc_explore_lock(sc->sc_udev); if (ch->operation != CHAN_OP_DRAIN) { if (ch->cur_alt == ch->set_alt && - ch->operation == CHAN_OP_NONE) { + ch->operation == CHAN_OP_NONE && + mtx_owned(ch->pcm_mtx) != 0) { /* save doing the explore task */ do_start = 1; } else { @@ -2459,28 +2521,81 @@ uaudio_chan_start(struct uaudio_chan *ch) &sc->sc_config_msg[0], &sc->sc_config_msg[1]); } } - usb_proc_explore_unlock(sc->sc_udev); - - /* reset feedback endpoint state */ - memset(&ch->feedback, 0, sizeof(ch->feedback)); - if (do_start) { usbd_transfer_start(ch->xfer[0]); usbd_transfer_start(ch->xfer[1]); } - return (0); } -int -uaudio_chan_stop(struct uaudio_chan *ch) +static int +uaudio_chan_need_both(struct uaudio_softc *sc) +{ + return (sc->sc_play_chan.num_alt > 0 && + sc->sc_play_chan.running != 0 && + uaudio_chan_is_async(&sc->sc_play_chan, + sc->sc_play_chan.set_alt) != 0 && + sc->sc_rec_chan.num_alt > 0 && + sc->sc_rec_chan.running == 0); +} + +static int +uaudio_chan_need_none(struct uaudio_softc *sc) +{ + return (sc->sc_play_chan.num_alt > 0 && + sc->sc_play_chan.running == 0 && + sc->sc_rec_chan.num_alt > 0 && + sc->sc_rec_chan.running == 0); +} + +void +uaudio_chan_start(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; - int do_stop = 0; + /* make operation atomic */ usb_proc_explore_lock(sc->sc_udev); + + /* check if not running */ + if (ch->running == 0) { + uint32_t temp; + + /* get current buffer size */ + temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt); + + /* set running flag */ + ch->running = 1; + + /* ensure the hardware buffer is reset */ + ch->start = ch->buf; + ch->end = ch->buf + temp; + ch->cur = ch->buf; + + if (uaudio_chan_need_both(sc)) { + /* + * Start both endpoints because of need for + * jitter information: + */ + uaudio_chan_start_sub(&sc->sc_rec_chan); + uaudio_chan_start_sub(&sc->sc_play_chan); + } else { + uaudio_chan_start_sub(ch); + } + } + + /* exit atomic operation */ + usb_proc_explore_unlock(sc->sc_udev); +} + +static void +uaudio_chan_stop_sub(struct uaudio_chan *ch) +{ + struct uaudio_softc *sc = ch->priv_sc; + int do_stop = 0; + if (ch->operation != CHAN_OP_DRAIN) { if (ch->cur_alt == ch->set_alt && - ch->operation == CHAN_OP_NONE) { + ch->operation == CHAN_OP_NONE && + mtx_owned(ch->pcm_mtx) != 0) { /* save doing the explore task */ do_stop = 1; } else { @@ -2489,13 +2604,44 @@ uaudio_chan_stop(struct uaudio_chan *ch) &sc->sc_config_msg[0], &sc->sc_config_msg[1]); } } - usb_proc_explore_unlock(sc->sc_udev); - if (do_stop) { usbd_transfer_stop(ch->xfer[0]); usbd_transfer_stop(ch->xfer[1]); } - return (0); +} + +void +uaudio_chan_stop(struct uaudio_chan *ch) +{ + struct uaudio_softc *sc = ch->priv_sc; + + /* make operation atomic */ + usb_proc_explore_lock(sc->sc_udev); + + /* check if running */ + if (ch->running != 0) { + /* clear running flag */ + ch->running = 0; + + if (uaudio_chan_need_both(sc)) { + /* + * Leave the endpoints running because we need + * information about jitter! + */ + } else if (uaudio_chan_need_none(sc)) { + /* + * Stop both endpoints in case the one was used for + * jitter information: + */ + uaudio_chan_stop_sub(&sc->sc_rec_chan); + uaudio_chan_stop_sub(&sc->sc_play_chan); + } else { + uaudio_chan_stop_sub(ch); + } + } + + /* exit atomic operation */ + usb_proc_explore_unlock(sc->sc_udev); } /*========================================================================* diff --git a/sys/dev/sound/usb/uaudio.h b/sys/dev/sound/usb/uaudio.h index 10c10de..2d9a875 100644 --- a/sys/dev/sound/usb/uaudio.h +++ b/sys/dev/sound/usb/uaudio.h @@ -54,8 +54,8 @@ extern struct pcmchan_matrix *uaudio_chan_getmatrix(struct uaudio_chan *ch, uint32_t format); extern int uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format); -extern int uaudio_chan_start(struct uaudio_chan *ch); -extern int uaudio_chan_stop(struct uaudio_chan *ch); +extern void uaudio_chan_start(struct uaudio_chan *ch); +extern void uaudio_chan_stop(struct uaudio_chan *ch); extern int uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m); extern int uaudio_mixer_uninit_sub(struct uaudio_softc *sc); diff --git a/sys/dev/sound/usb/uaudio_pcm.c b/sys/dev/sound/usb/uaudio_pcm.c index bd00a01..10e8cc4 100644 --- a/sys/dev/sound/usb/uaudio_pcm.c +++ b/sys/dev/sound/usb/uaudio_pcm.c @@ -81,14 +81,14 @@ ua_chan_setfragments(kobj_t obj, void *data, uint32_t blocksize, uint32_t blockc static int ua_chan_trigger(kobj_t obj, void *data, int go) { - if (!PCMTRIG_COMMON(go)) { - return (0); - } - if (go == PCMTRIG_START) { - return (uaudio_chan_start(data)); - } else { - return (uaudio_chan_stop(data)); + if (PCMTRIG_COMMON(go)) { + if (go == PCMTRIG_START) { + uaudio_chan_start(data); + } else { + uaudio_chan_stop(data); + } } + return (0); } static uint32_t |