From owner-svn-src-stable-8@FreeBSD.ORG Wed Mar 25 13:18:38 2015 Return-Path: Delivered-To: svn-src-stable-8@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 1258E46F; Wed, 25 Mar 2015 13:18:38 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id EE69529; Wed, 25 Mar 2015 13:18:37 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.9/8.14.9) with ESMTP id t2PDIbcs070798; Wed, 25 Mar 2015 13:18:37 GMT (envelope-from hselasky@FreeBSD.org) Received: (from hselasky@localhost) by svn.freebsd.org (8.14.9/8.14.9/Submit) id t2PDIbDi070795; Wed, 25 Mar 2015 13:18:37 GMT (envelope-from hselasky@FreeBSD.org) Message-Id: <201503251318.t2PDIbDi070795@svn.freebsd.org> X-Authentication-Warning: svn.freebsd.org: hselasky set sender to hselasky@FreeBSD.org using -f From: Hans Petter Selasky Date: Wed, 25 Mar 2015 13:18:37 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-8@freebsd.org Subject: svn commit: r280595 - stable/8/sys/dev/sound/usb X-SVN-Group: stable-8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-stable-8@freebsd.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: SVN commit messages for only the 8-stable src tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 25 Mar 2015 13:18:38 -0000 Author: hselasky Date: Wed Mar 25 13:18:36 2015 New Revision: 280595 URL: https://svnweb.freebsd.org/changeset/base/280595 Log: MFC r280322 and r280429: The synchronisation value returned by the so-called feedback endpoint appears to be too inaccurate that it can be used to synchronize the playback data stream. If there is a recording endpoint associated with the playback endpoint, use that instead. That means if the isochronous OUT endpoint is asynchronus the USB audio driver will automatically start recording, if possible, to get exact information about the needed sample rate adjustments. In no recording endpoint is present, no rate adaption will be done. While at it fix an issue where the hardware buffer pointers don't get reset at the first device PCM trigger. Make some variables 32-bit to avoid problems with multithreading. Use the feedback value from the synchronization endpoint as fallback when there is no recording channel. PR: 198444 Modified: stable/8/sys/dev/sound/usb/uaudio.c stable/8/sys/dev/sound/usb/uaudio.h stable/8/sys/dev/sound/usb/uaudio_pcm.c Directory Properties: stable/8/sys/ (props changed) stable/8/sys/dev/ (props changed) stable/8/sys/dev/sound/ (props changed) stable/8/sys/dev/sound/usb/ (props changed) Modified: stable/8/sys/dev/sound/usb/uaudio.c ============================================================================== --- stable/8/sys/dev/sound/usb/uaudio.c Wed Mar 25 13:16:39 2015 (r280594) +++ stable/8/sys/dev/sound/usb/uaudio.c Wed Mar 25 13:18:36 2015 (r280595) @@ -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_cla 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_s 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 us { 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 us 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 us 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 us } } +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_x 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); @@ -2245,33 +2302,46 @@ uaudio_chan_record_callback(struct usb_x 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 u 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 uaudi 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 uaud 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); } /*========================================================================* Modified: stable/8/sys/dev/sound/usb/uaudio.h ============================================================================== --- stable/8/sys/dev/sound/usb/uaudio.h Wed Mar 25 13:16:39 2015 (r280594) +++ stable/8/sys/dev/sound/usb/uaudio.h Wed Mar 25 13:18:36 2015 (r280595) @@ -54,8 +54,8 @@ extern struct pcmchan_matrix *uaudio_cha 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); Modified: stable/8/sys/dev/sound/usb/uaudio_pcm.c ============================================================================== --- stable/8/sys/dev/sound/usb/uaudio_pcm.c Wed Mar 25 13:16:39 2015 (r280594) +++ stable/8/sys/dev/sound/usb/uaudio_pcm.c Wed Mar 25 13:18:36 2015 (r280595) @@ -81,14 +81,14 @@ ua_chan_setfragments(kobj_t obj, void *d 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