summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/dev/sound/usb/uaudio.c393
-rw-r--r--sys/dev/sound/usb/uaudio.h4
-rw-r--r--sys/dev/sound/usb/uaudio_pcm.c14
3 files changed, 282 insertions, 129 deletions
diff --git a/sys/dev/sound/usb/uaudio.c b/sys/dev/sound/usb/uaudio.c
index ae93983..a6e8e6e 100644
--- a/sys/dev/sound/usb/uaudio.c
+++ b/sys/dev/sound/usb/uaudio.c
@@ -218,26 +218,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 */
@@ -1096,6 +1095,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:
@@ -1294,7 +1298,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);
@@ -1974,7 +1977,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;
@@ -2017,6 +2020,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;
@@ -2024,35 +2029,17 @@ 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":
+ * Use feedback value as fallback when there is no
+ * recording channel:
*/
- if (ch->feedback.constant > UAUDIO_IRQS)
- ch->feedback.constant = UAUDIO_IRQS;
- else if (ch->feedback.constant < -UAUDIO_IRQS)
- ch->feedback.constant = -UAUDIO_IRQS;
+ if (ch->priv_sc->sc_rec_chan.num_alt == 0)
+ ch->jitter_curr = temp - sample_rate;
+
+ ch->feedback_rate = temp;
break;
case USB_ST_SETUP:
@@ -2066,43 +2053,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) {
@@ -2118,6 +2160,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;
@@ -2133,31 +2178,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;
@@ -2165,28 +2202,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;
}
}
@@ -2202,36 +2236,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;
+ int expected_bytes;
+ int sample_size;
- usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
- mfl = usbd_xfer_max_framelen(xfer);
-
- 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);
@@ -2245,33 +2302,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;
}
}
@@ -2344,13 +2414,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);
}
@@ -2364,8 +2428,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 */
@@ -2376,7 +2443,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);
@@ -2447,16 +2516,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 {
@@ -2465,28 +2534,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 {
@@ -2495,13 +2617,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
OpenPOWER on IntegriCloud