diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 7fc1b14d57..3c655bce39 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -2542,7 +2542,12 @@ static int dbg_usb_audio_cb(int action, struct gui_synclist *lists) simplelist_addline("out ep: 0x%X in ep: 0x%X", usb_audio_get_out_ep(), usb_audio_get_in_ep()); simplelist_addline("Volume: %d", usb_audio_get_cur_volume()); simplelist_addline("Playback Frequency: %lu", usb_audio_get_playback_sampling_frequency()); - simplelist_addline("Buffers filled: %d", usb_audio_get_prebuffering()); + simplelist_addline("Frames dropped: %d", usb_audio_get_frames_dropped()); + simplelist_addline("Buffers filled: %f", (double)usb_audio_get_prebuffering_avg()/(1<<16)); // convert from 16.16 fixed to float + simplelist_addline("Min: %d / Max: %d", usb_audio_get_prebuffering_maxmin(false), usb_audio_get_prebuffering_maxmin(true)); + simplelist_addline("Samples used per Frame: %f", (double)usb_audio_get_samplesperframe()/(1<<16)); // convert from 16.16 fixed to float + simplelist_addline("Samples received per frame: %f", (double)usb_audio_get_samples_rx_perframe()/(1<<16)); // convert from 16.16 fixed to float + simplelist_addline("Samples diff: %f", (double)(usb_audio_get_samplesperframe()-usb_audio_get_samples_rx_perframe())/(1<<16)); // convert from 16.16 fixed to float simplelist_addline("%s", usb_audio_get_underflow()?"UNDERFLOW!":" "); simplelist_addline("%s", usb_audio_get_overflow()?"OVERFLOW!":" "); simplelist_addline("%s", usb_audio_get_alloc_failed()?"ALLOC FAILED!":" "); diff --git a/firmware/export/usb_ch9.h b/firmware/export/usb_ch9.h index cd1dc4c228..1fe281b77c 100644 --- a/firmware/export/usb_ch9.h +++ b/firmware/export/usb_ch9.h @@ -345,6 +345,10 @@ struct usb_endpoint_descriptor { #define USB_ENDPOINT_SYNC_ADAPTIVE (2 << 2) #define USB_ENDPOINT_SYNC_SYNC (3 << 2) +#define USB_ENDPOINT_USAGE_DATA (0 << 4) /* in bmAttributes */ +#define USB_ENDPOINT_USAGE_FEEDBACK (1 << 4) +#define USB_ENDPOINT_USAGE_IMPLICITFEEDBACK (2 << 4) + #define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* in bmAttributes */ #define USB_ENDPOINT_XFER_CONTROL 0 #define USB_ENDPOINT_XFER_ISOC 1 diff --git a/firmware/usbstack/usb_audio.c b/firmware/usbstack/usb_audio.c index 6907f4c8b2..239aea4d7c 100644 --- a/firmware/usbstack/usb_audio.c +++ b/firmware/usbstack/usb_audio.c @@ -16,6 +16,15 @@ * KIND, either express or implied. * ****************************************************************************/ + +/* NOTE + * + * This is USBAudio 1.0. USBAudio 2.0 is notably _not backwards compatible!_ + * USBAudio 1.0 over _USB_ 2.0 is perfectly valid! + * + * Relevant specifications are USB 2.0 and USB Audio Class 1.0. + */ + #include "string.h" #include "system.h" #include "usb_core.h" @@ -37,6 +46,11 @@ #define LOGF_ENABLE #include "logf.h" +// is there a "best practices" for converting between floats and fixed point? +// NOTE: SIGNED +#define TO_16DOT16_FIXEDPT(val) ((int32_t)(val) * (1<<16)) +#define TO_DOUBLE(val) ((double)(val) / (1<<16)) + /* Audio Control Interface */ static struct usb_interface_descriptor ac_interface = @@ -58,7 +72,7 @@ static struct usb_ac_header ac_header = .bLength = USB_AC_SIZEOF_HEADER(1), /* one interface */ .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_AC_HEADER, - .bcdADC = 0x0100, + .bcdADC = 0x0100, /* Identifies this as usb audio class 1.0 */ .wTotalLength = 0, /* fill later */ .bInCollection = 1, /* one interface */ .baInterfaceNr = {0}, /* fill later */ @@ -140,7 +154,7 @@ static struct usb_interface_descriptor .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 1, - .bNumEndpoints = 1, + .bNumEndpoints = 2, // iso audio, iso feedback .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, .bInterfaceProtocol = 0, @@ -176,35 +190,64 @@ static struct usb_as_format_type_i_discrete } }; -/* - * TODO: - * It appears that "Adaptive" sync mode means it it the device's duty - * to adapt its consumption rate of data to whatever the host sends, which - * has the possibility to cause trouble in underflows/overflows, etc. - * In practice, this is probably not a large concern, as we have a fairly large - * amount of buffering in the PCM system. - * An improvement may be to use "Asynchronous", but this is more complicated - * due to the need to inform the host about how fast the device will consume - * the data. - * So implementation of "Asynchronous" mode will be left for a later improvement. - */ -static struct usb_iso_audio_endpoint_descriptor - out_iso_ep = +static struct usb_as_iso_audio_endpoint + as_iso_audio_out_ep = { - .bLength = sizeof(struct usb_iso_audio_endpoint_descriptor), + .bLength = sizeof(struct usb_as_iso_audio_endpoint), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, /* filled later */ - .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE, + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC | USB_ENDPOINT_USAGE_DATA, .wMaxPacketSize = 0, /* filled later */ - .bInterval = 0, /* filled later */ + .bInterval = 0, /* filled later - 1 for full speed, 4 for high-speed */ .bRefresh = 0, - .bSynchAddress = 0 /* filled later */ + .bSynchAddress = 0 /* filled later to the address of as_iso_synch_in_ep */ }; -static struct usb_as_iso_endpoint - as_out_iso_ep = +/* + * Updaing the desired sample frequency: + * + * The iso OUT ep is inextricably linked to the feedback iso IN ep + * when using Asynchronous mode. It periodically describes to the host + * how fast to send the data. + * + * Some notes from the usbaudio 1.0 documentation: + * - bSyncAddress of the iso OUT ep must be set to the address of the iso IN feedback ep + * - bSyncAddress of the iso IN feedback ep must be zero + * - F_f (desired sampling frequency) describes directly the number of samples the endpoint + * wants to receive per frame to match the actual sampling frequency F_s + * - There is a value, (2^(10-P)), which is how often (in 1mS frames) the F_f value will be sent + * - P appears to be somewhat arbitrary, though the spec wants it to relate the real sample rate + * F_s to the master clock rate F_m by the relationship (F_m = F_s * (2^(P-1))) + * - The above description of P is somewhat moot because of how much buffering we have. I suspect it + * was written for devices with essentially zero buffering. + * - bRefresh of the feedback endpoint descriptor should be set to (10-P). This can range from 1 to 9. + * A value of 1 would mean refreshing every 2^1 mS = 2 mS, a value of 9 would mean refreshing every + * 2^9 mS = 512 mS. + * - The F_f value should be encoded in "10.10" format, but justified to the leftmost 24 bits, + * so it ends up looking like "10.14" format. This format is 3 bytes long. On USB 2.0, it seems that + * the USB spec overrides the UAC 1.0 spec here, so high-speed bus operation needs "16.16" format, + * in a 4 byte packet. + */ +#define FEEDBACK_UPDATE_RATE_P 5 +#define FEEDBACK_UPDATE_RATE_REFRESH (10-FEEDBACK_UPDATE_RATE_P) +#define FEEDBACK_UPDATE_RATE_FRAMES (0x1<>8) & 0xFF; + arr[2] = (fixedpt>>16) & 0xFF; + arr[3] = (fixedpt>>24) & 0xFF; + } + else // full-speed + { + // Q16.16 --> Q10.10 --> Q10.14 + fixedpt = value / (1<<2); // convert from Q16.16 to Q10.14 + + // then aligned so it's more like Q10.14 + // NOTE: this line left for posterity + // fixedpt = fixedpt << (4); + + arr[0] = (fixedpt & 0xFF); + arr[1] = (fixedpt>>8) & 0xFF; + arr[2] = (fixedpt>>16) & 0xFF; + } + +} + static void set_playback_sampling_frequency(unsigned long f) { // only values 44.1k and higher (array is in descending order) @@ -373,8 +479,7 @@ static void set_playback_sampling_frequency(unsigned long f) unsigned long usb_audio_get_playback_sampling_frequency(void) { - logf("usbaudio: get playback sampl freq %lu Hz", - hw_freq_sampr[as_playback_freq_idx]); + // logf("usbaudio: get playback sampl freq %lu Hz", hw_freq_sampr[as_playback_freq_idx]); return hw_freq_sampr[as_playback_freq_idx]; } @@ -382,7 +487,7 @@ void usb_audio_init(void) { unsigned int i; /* initialized tSamFreq array */ - logf("usbaudio: supported frequencies"); + logf("usbaudio: (init) supported frequencies"); // only values 44.1k and higher (array is in descending order) for(i = 0; i <= HW_FREQ_44; i++) { @@ -393,7 +498,6 @@ void usb_audio_init(void) int usb_audio_request_buf(void) { - // stop playback first thing audio_stop(); @@ -439,18 +543,21 @@ int usb_audio_request_endpoints(struct usb_class_driver *drv) return -1; } - in_iso_ep_adr = usb_core_request_endpoint(USB_ENDPOINT_XFER_ISOC, USB_DIR_IN, drv); - if(in_iso_ep_adr < 0) + in_iso_feedback_ep_adr = usb_core_request_endpoint(USB_ENDPOINT_XFER_ISOC, USB_DIR_IN, drv); + if(in_iso_feedback_ep_adr < 0) { usb_core_release_endpoint(out_iso_ep_adr); - logf("usbaudio: cannot get an out iso endpoint"); + logf("usbaudio: cannot get an in iso endpoint"); return -1; } - logf("usbaudio: iso out ep is 0x%x, in ep is 0x%x", out_iso_ep_adr, in_iso_ep_adr); + logf("usbaudio: iso out ep is 0x%x, in ep is 0x%x", out_iso_ep_adr, in_iso_feedback_ep_adr); - out_iso_ep.bEndpointAddress = out_iso_ep_adr; - out_iso_ep.bSynchAddress = 0; + as_iso_audio_out_ep.bEndpointAddress = out_iso_ep_adr; + as_iso_audio_out_ep.bSynchAddress = in_iso_feedback_ep_adr; + + as_iso_synch_in_ep.bEndpointAddress = in_iso_feedback_ep_adr; + as_iso_synch_in_ep.bSynchAddress = 0; return 0; } @@ -462,7 +569,7 @@ unsigned int usb_audio_get_out_ep(void) unsigned int usb_audio_get_in_ep(void) { - return in_iso_ep_adr; + return in_iso_feedback_ep_adr; } int usb_audio_set_first_interface(int interface) @@ -478,7 +585,7 @@ int usb_audio_get_config_descriptor(unsigned char *dest, int max_packet_size) unsigned int i; unsigned char *orig_dest = dest; - // logf("get config descriptors"); + logf("get config descriptors"); /** Configuration */ @@ -498,16 +605,23 @@ int usb_audio_get_config_descriptor(unsigned char *dest, int max_packet_size) as_interface_alt_playback.bInterfaceNumber = usb_interface + 1; /* endpoints */ - out_iso_ep.wMaxPacketSize = 1023; /* one micro-frame per transaction */ + as_iso_audio_out_ep.wMaxPacketSize = 1023; + /** Endpoint Interval calculation: * typically sampling frequency is 44100 Hz and top is 192000 Hz, which * account for typical 44100*2(stereo)*2(16-bit) ~= 180 kB/s - * and top 770 kB/s. Since there are 1000 frames per seconds and maximum + * and top 770 kB/s. Since there are ~1000 frames per seconds and maximum * packet size is set to 1023, one transaction per frame is good enough - * for over 1 MB/s. At high-speed, add 3 to this value because there are - * 8 = 2^3 micro-frames per frame. + * for over 1 MB/s. * Recall that actual is 2^(bInterval - 1) */ - out_iso_ep.bInterval = usb_drv_port_speed() ? 4 : 1; + + /* In simpler language, This is intended to emulate full-speed's + * one-packet-per-frame rate on high-speed, where we have multiple microframes per millisecond. + */ + as_iso_audio_out_ep.bInterval = usb_drv_port_speed() ? 4 : 1; + as_iso_synch_in_ep.bInterval = usb_drv_port_speed() ? 4 : 1; // per spec + + logf("usbaudio: port_speed=%s", usb_drv_port_speed()?"hs":"fs"); /** Packing */ for(i = 0; i < USB_DESCRIPTORS_LIST_SIZE; i++) @@ -535,6 +649,7 @@ static void playback_audio_get_more(const void **start, size_t *size) *start = rx_buffer + (rx_play_idx * REAL_BUF_SIZE); *size = rx_buf_size[rx_play_idx]; rx_play_idx = (rx_play_idx + 1) % NR_BUFFERS; + /* if usb RX buffers had overflowed, we can start to receive again * guard against IRQ to avoid race with completion usb completion (although * this function is probably running in IRQ context anyway) */ @@ -556,6 +671,23 @@ static void usb_audio_start_playback(void) rx_play_idx = 0; rx_usb_idx = 0; + // feedback initialization + fb_startframe = usb_drv_get_frame_number(); + samples_fb = 0; + samples_received_report = 0; + + // debug screen info - frame drop counter + frames_dropped = 0; + last_frame = -1; + buffers_filled_min = -1; + buffers_filled_min_last = -1; + buffers_filled_max = -1; + buffers_filled_max_last = -1; + + // debug screen info - sample counters + samples_received = 0; + samples_received_last = 0; + // TODO: implement recording from the USB stream #if (INPUT_SRC_CAPS != 0) audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); @@ -570,12 +702,13 @@ static void usb_audio_start_playback(void) static void usb_audio_stop_playback(void) { - // logf("usbaudio: stop playback"); + logf("usbaudio: stop playback"); if(usb_audio_playing) { mixer_channel_stop(PCM_MIXER_CHAN_USBAUDIO); usb_audio_playing = false; } + send_fb = false; } int usb_audio_set_interface(int intf, int alt) @@ -642,8 +775,18 @@ int usb_audio_get_alt_intf(void) { return usb_as_playback_intf_alt; } + +int32_t usb_audio_get_samplesperframe(void) +{ + return samples_fb; +} -static bool usb_audio_as_playback_endpoint_request(struct usb_ctrlrequest* req, void *reqdata) +int32_t usb_audio_get_samples_rx_perframe(void) +{ + return samples_received_report; +} + +static bool usb_audio_as_ctrldata_endpoint_request(struct usb_ctrlrequest* req, void *reqdata) { /* only support sampling frequency */ if(req->wValue != (USB_AS_EP_CS_SAMPLING_FREQ_CTL << 8)) @@ -707,7 +850,7 @@ static bool usb_audio_endpoint_request(struct usb_ctrlrequest* req, void *reqdat int ep = req->wIndex & 0xff; if(ep == out_iso_ep_adr) - return usb_audio_as_playback_endpoint_request(req, reqdata); + return usb_audio_as_ctrldata_endpoint_request(req, reqdata); else { logf("usbaudio: unhandled ep req (ep=%d)", ep); @@ -727,7 +870,7 @@ static bool feature_unit_set_mute(int value, uint8_t cmd) { logf("usbaudio: mute !"); tmp_saved_vol = sound_current(SOUND_VOLUME); - // sound_set_volume(sound_min(SOUND_VOLUME)); + // setvol does range checking for us! global_status.volume = sound_min(SOUND_VOLUME); setvol(); @@ -736,7 +879,7 @@ static bool feature_unit_set_mute(int value, uint8_t cmd) else if(value == 0) { logf("usbaudio: not muted !"); - // sound_set_volume(tmp_saved_vol); + // setvol does range checking for us! global_status.volume = tmp_saved_vol; setvol(); @@ -808,7 +951,6 @@ static bool feature_unit_set_volume(int value, uint8_t cmd) logf("usbaudio: set volume=%d dB", usb_audio_volume_to_db(value, sound_numdecimals(SOUND_VOLUME))); - // sound_set_volume(usb_audio_volume_to_db(value, sound_numdecimals(SOUND_VOLUME))); // setvol does range checking for us! // we cannot guarantee the host will send us a volume within our range global_status.volume = usb_audio_volume_to_db(value, sound_numdecimals(SOUND_VOLUME)); @@ -829,7 +971,7 @@ static bool feature_unit_get_volume(int *value, uint8_t cmd) return false; } - logf("usbaudio: get %s volume=%d dB", usb_audio_ac_ctl_req_str(cmd), usb_audio_volume_to_db(*value, sound_numdecimals(SOUND_VOLUME))); + // logf("usbaudio: get %s volume=%d dB", usb_audio_ac_ctl_req_str(cmd), usb_audio_volume_to_db(*value, sound_numdecimals(SOUND_VOLUME))); return true; } @@ -856,7 +998,7 @@ static bool usb_audio_set_get_feature_unit(struct usb_ctrlrequest* req, void *re return false; } /* selectors */ - /* all send/received values are integers so already read data if necessary and store in it in an integer */ + /* all send/received values are integers already - read data if necessary and store in it in an integer */ if(req->bRequest & USB_AC_GET_REQ) { /* get */ @@ -987,9 +1129,10 @@ static bool usb_audio_interface_request(struct usb_ctrlrequest* req, void *reqda } } -bool usb_audio_control_request(struct usb_ctrlrequest* req, void *reqdata) +bool usb_audio_control_request(struct usb_ctrlrequest* req, void *reqdata, unsigned char* dest) { (void) reqdata; + (void) dest; switch(req->bRequestType & USB_RECIP_MASK) { @@ -1034,7 +1177,7 @@ bool usb_audio_get_playing(void) /* determine if enough prebuffering has been done to restart audio */ bool prebuffering_done(void) { - /* restart audio if at least two buffers are filled */ + /* restart audio if at least MINIMUM_BUFFERS_QUEUED buffers are filled */ int diff = (rx_usb_idx - rx_play_idx + NR_BUFFERS) % NR_BUFFERS; return diff >= MINIMUM_BUFFERS_QUEUED; } @@ -1044,6 +1187,28 @@ int usb_audio_get_prebuffering(void) return (rx_usb_idx - rx_play_idx + NR_BUFFERS) % NR_BUFFERS; } +int32_t usb_audio_get_prebuffering_avg(void) +{ + if (buffers_filled_avgcount == 0) + { + return TO_16DOT16_FIXEDPT(usb_audio_get_prebuffering()); + } else { + return (TO_16DOT16_FIXEDPT(buffers_filled_accumulator)/buffers_filled_avgcount) + TO_16DOT16_FIXEDPT(MINIMUM_BUFFERS_QUEUED); + } +} + +int usb_audio_get_prebuffering_maxmin(bool max) +{ + if (max) + { + return buffers_filled_max == -1 ? buffers_filled_max_last : buffers_filled_max; + } + else + { + return buffers_filled_min == -1 ? buffers_filled_min_last : buffers_filled_min; + } +} + bool usb_audio_get_underflow(void) { return playback_audio_underflow; @@ -1054,6 +1219,11 @@ bool usb_audio_get_overflow(void) return usb_rx_overflow; } +int usb_audio_get_frames_dropped(void) +{ + return frames_dropped; +} + void usb_audio_transfer_complete(int ep, int dir, int status, int length) { /* normal handler is too slow to handle the completion rate, because @@ -1067,15 +1237,37 @@ void usb_audio_transfer_complete(int ep, int dir, int status, int length) bool usb_audio_fast_transfer_complete(int ep, int dir, int status, int length) { (void) dir; + bool retval = false; if(ep == out_iso_ep_adr && usb_as_playback_intf_alt == 1) { - // logf("usbaudio: frame: %d", usb_drv_get_frame_number()); + // check for dropped frames + if (last_frame != usb_drv_get_frame_number()) + { + if ((((last_frame + 1) % (USB_FRAME_MAX + 1)) != usb_drv_get_frame_number()) && (last_frame != -1)) + { + frames_dropped++; + } + last_frame = usb_drv_get_frame_number(); + } + + // If audio and feedback EPs happen to have the same base number (with opposite directions, of course), + // we will get replies to the feedback here, don't want that to be interpreted as data. + if (length <= 4) + { + return true; + } + + logf("usbaudio: frame: %d bytes: %d", usb_drv_get_frame_number(), length); if(status != 0) return true; /* FIXME how to handle error here ? */ /* store length, queue buffer */ rx_buf_size[rx_usb_idx] = length; rx_usb_idx = (rx_usb_idx + 1) % NR_BUFFERS; + + // debug screen counter + samples_received = samples_received + length; + /* guard against IRQ to avoid race with completion audio completion */ int oldlevel = disable_irq_save(); /* setup a new transaction except if we ran out of buffers */ @@ -1098,8 +1290,88 @@ bool usb_audio_fast_transfer_complete(int ep, int dir, int status, int length) mixer_channel_play_data(PCM_MIXER_CHAN_USBAUDIO, playback_audio_get_more, NULL, 0); } restore_irq(oldlevel); - return true; + retval = true; } else - return false; + { + retval = false; + } + + // send feedback value every N frames! + // NOTE: important that we need to queue this up _the frame before_ it's needed - on MacOS especially! + if ((usb_drv_get_frame_number()+1) % FEEDBACK_UPDATE_RATE_FRAMES == 0 && send_fb) + { + if (!sent_fb_this_frame) + { + /* NOTE: the division of frequency must be staged to avoid overflow of 16-bit signed int + * as well as truncating the result to ones place! + * Must avoid values > 32,768 (2^15) + * Largest value: 192,000 --> /10: 19,200 --> /100: 192 + * Smallest value: 44,100 --> /10: 4,410 --> /100: 44.1 + */ + int32_t samples_base = TO_16DOT16_FIXEDPT(hw_freq_sampr[as_playback_freq_idx]/10)/100; + int32_t buffers_filled = 0; + + if (buffers_filled_avgcount != 0) + { + buffers_filled = TO_16DOT16_FIXEDPT((int32_t)buffers_filled_accumulator) / buffers_filled_avgcount; + } + buffers_filled_accumulator = buffers_filled_accumulator - buffers_filled_accumulator_old; + buffers_filled_avgcount = buffers_filled_avgcount - buffers_filled_avgcount_old; + buffers_filled_accumulator_old = buffers_filled_accumulator; + buffers_filled_avgcount_old = buffers_filled_avgcount; + + // someone who has implemented actual PID before might be able to do this correctly, + // but this seems to work good enough? + // Coefficients were 1, 0.25, 0.025 in float math --> 1, /4, /40 in fixed-point math + samples_fb = samples_base - (buffers_filled/4) + ((buffers_filled_old - buffers_filled)/40); + buffers_filled_old = buffers_filled; + + // must limit to +/- 1 sample from nominal + samples_fb = samples_fb > (samples_base + TO_16DOT16_FIXEDPT(1)) ? samples_base + TO_16DOT16_FIXEDPT(1) : samples_fb; + samples_fb = samples_fb < (samples_base - TO_16DOT16_FIXEDPT(1)) ? samples_base - TO_16DOT16_FIXEDPT(1) : samples_fb; + + encodeFBfixedpt(sendFf, samples_fb, usb_drv_port_speed()); + logf("usbaudio: frame %d fbval 0x%02X%02X%02X%02X", usb_drv_get_frame_number(), sendFf[3], sendFf[2], sendFf[1], sendFf[0]); + usb_drv_send_nonblocking(in_iso_feedback_ep_adr, sendFf, usb_drv_port_speed()?4:3); + + // debug screen counters + // + // samples_received NOTE: need some "division staging" to not overflow signed 16-bit value + // samples / (feedback frames * 2) --> samples/2 + // samples_report / (2ch * 2bytes per sample) --> samples/4 + // total: samples/8 + samples_received_report = TO_16DOT16_FIXEDPT(samples_received/8) / FEEDBACK_UPDATE_RATE_FRAMES; + samples_received = samples_received - samples_received_last; + samples_received_last = samples_received; + buffers_filled_max_last = buffers_filled_max; + buffers_filled_max = -1; + buffers_filled_min_last = buffers_filled_min; + buffers_filled_min = -1; + } + sent_fb_this_frame = true; + } + else + { + sent_fb_this_frame = false; + if (!send_fb) + { + // arbitrary wait during startup + if (usb_drv_get_frame_number() == (fb_startframe + (FEEDBACK_UPDATE_RATE_FRAMES*2))%(USB_FRAME_MAX+1)) + { + send_fb = true; + } + } + buffers_filled_accumulator = buffers_filled_accumulator + (usb_audio_get_prebuffering() - MINIMUM_BUFFERS_QUEUED); + buffers_filled_avgcount++; + if (usb_audio_get_prebuffering() < buffers_filled_min || buffers_filled_min == -1) + { + buffers_filled_min = usb_audio_get_prebuffering(); + } else if (usb_audio_get_prebuffering() > buffers_filled_max) + { + buffers_filled_max = usb_audio_get_prebuffering(); + } + } + + return retval; } diff --git a/firmware/usbstack/usb_audio.h b/firmware/usbstack/usb_audio.h index e713f8f92b..209efccaa6 100644 --- a/firmware/usbstack/usb_audio.h +++ b/firmware/usbstack/usb_audio.h @@ -21,6 +21,14 @@ #include "usb_ch9.h" +/* NOTE + * + * This is USBAudio 1.0. USBAudio 2.0 is notably _not backwards compatible!_ + * USBAudio 1.0 over _USB_ 2.0 is perfectly valid! + * + * Relevant specifications are USB 2.0 and USB Audio Class 1.0. + */ + /* * usb_audio_request_endpoints(): * @@ -178,6 +186,28 @@ int usb_audio_get_main_intf(void); */ int usb_audio_get_alt_intf(void); +/* + * usb_audio_get_samplesperframe(): + * + * Return the samples per frame over the last two feedback cycles + * This is the samples sent to the mixer. + * + * This is returned in floating point 16.16 type. To convert to float, + * do ((double)result / (1<<16)) + */ +int32_t usb_audio_get_samplesperframe(void); + +/* + * usb_audio_get_samplesperframe(): + * + * Return the samples per frame over the last two feedback cycles + * This is the samples received from USB. + * + * This is returned in floating point 16.16 type. To convert to float, + * do ((double)result / (1<<16)) + */ +int32_t usb_audio_get_samples_rx_perframe(void); + /* * usb_audio_get_out_ep(): * @@ -199,6 +229,25 @@ unsigned int usb_audio_get_in_ep(void); */ int usb_audio_get_prebuffering(void); +/* + * usb_audio_get_prebuffering_avg(): + * + * Return the average number of buffers filled ahead of playback + * over the last two feedback cycles + * + * This is returned in floating point 16.16 type. To convert to float, + * do ((double)result / (1<<16)) + */ +int32_t usb_audio_get_prebuffering_avg(void); + +/* + * usb_audio_get_prebuffering_maxmin(): + * + * Return the max or min number of buffers filled ahead of playback + * over the last feedback cycle + */ +int usb_audio_get_prebuffering_maxmin(bool max); + /* * usb_audio_get_underflow(): * @@ -213,6 +262,13 @@ bool usb_audio_get_underflow(void); */ bool usb_audio_get_overflow(void); +/* + * usb_audio_get_frames_dropped(): + * + * Return the number of frames which have been dropped during playback + */ +int usb_audio_get_frames_dropped(void); + /* * usb_audio_get_cur_volume(): * diff --git a/firmware/usbstack/usb_audio_def.h b/firmware/usbstack/usb_audio_def.h index 87bfe7ece9..b641ac15f2 100644 --- a/firmware/usbstack/usb_audio_def.h +++ b/firmware/usbstack/usb_audio_def.h @@ -18,6 +18,14 @@ ****************************************************************************/ /* Parts of this file are based on Frank Gevaerts work and on audio.h from linux */ +/* NOTE + * + * This is USBAudio 1.0. USBAudio 2.0 is notably _not backwards compatible!_ + * USBAudio 1.0 over _USB_ 2.0 is perfectly valid! + * + * Relevant specifications are USB 2.0 and USB Audio Class 1.0. + */ + #ifndef USB_AUDIO_DEF_H #define USB_AUDIO_DEF_H @@ -188,25 +196,54 @@ struct usb_as_interface { #define USB_AS_EP_CS_SAMPLING_FREQ_CTL 0x01 #define USB_AS_EP_CS_PITCH_CTL 0x02 -struct usb_iso_audio_endpoint_descriptor { +// Standard* AudioStreaming Isochronous Audio Data Endpoint Descriptor +// +// *Note, per usbaudio 1.0 this is explititly identical +// to a standard endpoint descriptor but with extra information +struct usb_as_iso_audio_endpoint { uint8_t bLength; uint8_t bDescriptorType; uint8_t bEndpointAddress; uint8_t bmAttributes; uint16_t wMaxPacketSize; - uint8_t bInterval; - uint8_t bRefresh; - uint8_t bSynchAddress; + uint8_t bInterval; /* MUST be set to 1 (full speed), +3 for high-speed? */ + uint8_t bRefresh; /* USB Audio Class 1.0 specific: set to 0 */ + uint8_t bSynchAddress; /* USB Audio Class 1.0 specific: used to identify synch ep */ } __attribute__ ((packed)); -struct usb_as_iso_endpoint { +// Class-Specific AudioStreaming Isochronous Audio* (Control) Data Endpoint Descriptor +// +// *The name is a bit misleading, this is for _control_ of audio, +// such as sampling frequency or pitch. +struct usb_as_iso_ctrldata_endpoint { uint8_t bLength; uint8_t bDescriptorType; /* USB_DT_CS_ENDPOINT */ uint8_t bDescriptorSubType; /* USB_AS_EP_GENERAL */ uint8_t bmAttributes; - uint8_t bLockDelayUnits; - uint16_t wLockDelay; + uint8_t bLockDelayUnits; /* USB Audio Class 1.0 specific */ + uint16_t wLockDelay; /* USB Audio Class 1.0 specific */ +} __attribute__ ((packed)); + +// Standard* AudioStreaming Isochronous Synch (Feedback) Endpoint Descriptor +// +// *Note, per usbaudio 1.0 this is explititly identical +// to a standard endpoint descriptor but with extra information +struct usb_as_iso_synch_endpoint { + uint8_t bLength; + uint8_t bDescriptorType; /* USB_DT_CS_ENDPOINT */ + + uint8_t bEndpointAddress; /* D7: direction (0 = source, 1 = sink) + * D6..4: Zeros + * D3..0: EP Number */ + uint8_t bmAttributes; /* USBAudio 1.0: + * D3..2: Synch type (00 = None) + * D1..0: Transfer type (01 = Isochronous) + */ + uint16_t wMaxPacketSize; + uint8_t bInterval; /* MUST be set to 1 (full speed), +3 for high-speed? */ + uint8_t bRefresh; /* USB Audio Class 1.0 specific: Range of 1 to 9 (full-speed). high-speed, probably range of 1 to 16? */ + uint8_t bSynchAddress; /* USB Audio Class 1.0 specific: MUST be set to 0 */ } __attribute__ ((packed)); #define USB_AS_FORMAT_TYPE_I_UNDEFINED 0x0