mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-12-06 21:25:03 -05:00
usbaudio: convert to asynchronous operation
Add feedback not based on samples used, but on buffers filled - idea
being we can do "PID" (someone who has actually implemented Real PID
could probably rewrite the calculation) based on how many buffers
we have filled versus the ideal buffer filled level (16).
Feedback is based on a historical average of the last two feedback
intervals.
This feedback math is done as fixed-point math to keep floats out of core. Note that a couple division operations needed to be strategically staged to avoid overflow or truncation.
Floats are still used for debug screen printout.
Also fixed a typo in the definition of usb_audio_control_request()
Host:
Linux: works
MacOS: works
Windows: Feedback does not work! It appears that Windows may not
support asynchronous devices at all. Playback may "work",
but results will vary as the number of buffers filled will
drift over time.
Change-Id: I027feb16705e6e46c1144b1d08920b53de42cb26
This commit is contained in:
parent
9ce66e088e
commit
7c4293af64
5 changed files with 439 additions and 65 deletions
|
|
@ -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("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("Volume: %d", usb_audio_get_cur_volume());
|
||||||
simplelist_addline("Playback Frequency: %lu", usb_audio_get_playback_sampling_frequency());
|
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_underflow()?"UNDERFLOW!":" ");
|
||||||
simplelist_addline("%s", usb_audio_get_overflow()?"OVERFLOW!":" ");
|
simplelist_addline("%s", usb_audio_get_overflow()?"OVERFLOW!":" ");
|
||||||
simplelist_addline("%s", usb_audio_get_alloc_failed()?"ALLOC FAILED!":" ");
|
simplelist_addline("%s", usb_audio_get_alloc_failed()?"ALLOC FAILED!":" ");
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,10 @@ struct usb_endpoint_descriptor {
|
||||||
#define USB_ENDPOINT_SYNC_ADAPTIVE (2 << 2)
|
#define USB_ENDPOINT_SYNC_ADAPTIVE (2 << 2)
|
||||||
#define USB_ENDPOINT_SYNC_SYNC (3 << 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_XFERTYPE_MASK 0x03 /* in bmAttributes */
|
||||||
#define USB_ENDPOINT_XFER_CONTROL 0
|
#define USB_ENDPOINT_XFER_CONTROL 0
|
||||||
#define USB_ENDPOINT_XFER_ISOC 1
|
#define USB_ENDPOINT_XFER_ISOC 1
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,15 @@
|
||||||
* KIND, either express or implied.
|
* 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 "string.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "usb_core.h"
|
#include "usb_core.h"
|
||||||
|
|
@ -37,6 +46,11 @@
|
||||||
#define LOGF_ENABLE
|
#define LOGF_ENABLE
|
||||||
#include "logf.h"
|
#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 */
|
/* Audio Control Interface */
|
||||||
static struct usb_interface_descriptor
|
static struct usb_interface_descriptor
|
||||||
ac_interface =
|
ac_interface =
|
||||||
|
|
@ -58,7 +72,7 @@ static struct usb_ac_header ac_header =
|
||||||
.bLength = USB_AC_SIZEOF_HEADER(1), /* one interface */
|
.bLength = USB_AC_SIZEOF_HEADER(1), /* one interface */
|
||||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||||
.bDescriptorSubType = USB_AC_HEADER,
|
.bDescriptorSubType = USB_AC_HEADER,
|
||||||
.bcdADC = 0x0100,
|
.bcdADC = 0x0100, /* Identifies this as usb audio class 1.0 */
|
||||||
.wTotalLength = 0, /* fill later */
|
.wTotalLength = 0, /* fill later */
|
||||||
.bInCollection = 1, /* one interface */
|
.bInCollection = 1, /* one interface */
|
||||||
.baInterfaceNr = {0}, /* fill later */
|
.baInterfaceNr = {0}, /* fill later */
|
||||||
|
|
@ -140,7 +154,7 @@ static struct usb_interface_descriptor
|
||||||
.bDescriptorType = USB_DT_INTERFACE,
|
.bDescriptorType = USB_DT_INTERFACE,
|
||||||
.bInterfaceNumber = 0,
|
.bInterfaceNumber = 0,
|
||||||
.bAlternateSetting = 1,
|
.bAlternateSetting = 1,
|
||||||
.bNumEndpoints = 1,
|
.bNumEndpoints = 2, // iso audio, iso feedback
|
||||||
.bInterfaceClass = USB_CLASS_AUDIO,
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
||||||
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
|
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
|
||||||
.bInterfaceProtocol = 0,
|
.bInterfaceProtocol = 0,
|
||||||
|
|
@ -176,35 +190,64 @@ static struct usb_as_format_type_i_discrete
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
static struct usb_as_iso_audio_endpoint
|
||||||
* TODO:
|
as_iso_audio_out_ep =
|
||||||
* 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 =
|
|
||||||
{
|
{
|
||||||
.bLength = sizeof(struct usb_iso_audio_endpoint_descriptor),
|
.bLength = sizeof(struct usb_as_iso_audio_endpoint),
|
||||||
.bDescriptorType = USB_DT_ENDPOINT,
|
.bDescriptorType = USB_DT_ENDPOINT,
|
||||||
.bEndpointAddress = USB_DIR_OUT, /* filled later */
|
.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 */
|
.wMaxPacketSize = 0, /* filled later */
|
||||||
.bInterval = 0, /* filled later */
|
.bInterval = 0, /* filled later - 1 for full speed, 4 for high-speed */
|
||||||
.bRefresh = 0,
|
.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<<FEEDBACK_UPDATE_RATE_REFRESH)
|
||||||
|
static struct usb_as_iso_synch_endpoint
|
||||||
|
as_iso_synch_in_ep =
|
||||||
{
|
{
|
||||||
.bLength = sizeof(struct usb_as_iso_endpoint),
|
.bLength = sizeof(struct usb_as_iso_synch_endpoint),
|
||||||
|
.bDescriptorType = USB_DT_ENDPOINT,
|
||||||
|
.bEndpointAddress = USB_DIR_IN, /* filled later */
|
||||||
|
.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_NONE | USB_ENDPOINT_USAGE_FEEDBACK,
|
||||||
|
.wMaxPacketSize = 4,
|
||||||
|
.bInterval = 0, /* filled later - 1 or 4 depending on bus speed */
|
||||||
|
.bRefresh = FEEDBACK_UPDATE_RATE_REFRESH, /* This describes how often this ep will update F_f (see above) */
|
||||||
|
.bSynchAddress = 0 /* MUST be zero! */
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct usb_as_iso_ctrldata_endpoint
|
||||||
|
as_iso_ctrldata_samfreq =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(struct usb_as_iso_ctrldata_endpoint),
|
||||||
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
||||||
.bDescriptorSubType = USB_AS_EP_GENERAL,
|
.bDescriptorSubType = USB_AS_EP_GENERAL,
|
||||||
.bmAttributes = USB_AS_EP_CS_SAMPLING_FREQ_CTL,
|
.bmAttributes = USB_AS_EP_CS_SAMPLING_FREQ_CTL,
|
||||||
|
|
@ -216,12 +259,14 @@ static const struct usb_descriptor_header* const ac_cs_descriptors_list[] =
|
||||||
{
|
{
|
||||||
(struct usb_descriptor_header *) &ac_header,
|
(struct usb_descriptor_header *) &ac_header,
|
||||||
(struct usb_descriptor_header *) &ac_playback_input,
|
(struct usb_descriptor_header *) &ac_playback_input,
|
||||||
|
(struct usb_descriptor_header *) &ac_playback_feature,
|
||||||
(struct usb_descriptor_header *) &ac_playback_output,
|
(struct usb_descriptor_header *) &ac_playback_output,
|
||||||
(struct usb_descriptor_header *) &ac_playback_feature
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#define AC_CS_DESCRIPTORS_LIST_SIZE (sizeof(ac_cs_descriptors_list)/sizeof(ac_cs_descriptors_list[0]))
|
#define AC_CS_DESCRIPTORS_LIST_SIZE (sizeof(ac_cs_descriptors_list)/sizeof(ac_cs_descriptors_list[0]))
|
||||||
|
|
||||||
|
// TODO: AudioControl Interrupt endpoint to inform the host that changes were made on-device!
|
||||||
|
// The most immediately useful of this capability is volume and mute changes.
|
||||||
static const struct usb_descriptor_header* const usb_descriptors_list[] =
|
static const struct usb_descriptor_header* const usb_descriptors_list[] =
|
||||||
{
|
{
|
||||||
/* Audio Control */
|
/* Audio Control */
|
||||||
|
|
@ -237,8 +282,11 @@ static const struct usb_descriptor_header* const usb_descriptors_list[] =
|
||||||
(struct usb_descriptor_header *) &as_interface_alt_playback,
|
(struct usb_descriptor_header *) &as_interface_alt_playback,
|
||||||
(struct usb_descriptor_header *) &as_playback_cs_interface,
|
(struct usb_descriptor_header *) &as_playback_cs_interface,
|
||||||
(struct usb_descriptor_header *) &as_playback_format_type_i,
|
(struct usb_descriptor_header *) &as_playback_format_type_i,
|
||||||
(struct usb_descriptor_header *) &out_iso_ep,
|
/* NOTE: the order of these three is important for maximum compatibility.
|
||||||
(struct usb_descriptor_header *) &as_out_iso_ep,
|
* Synch ep should follow iso out ep, with ctrldata descriptor coming first. */
|
||||||
|
(struct usb_descriptor_header *) &as_iso_ctrldata_samfreq,
|
||||||
|
(struct usb_descriptor_header *) &as_iso_audio_out_ep,
|
||||||
|
(struct usb_descriptor_header *) &as_iso_synch_in_ep,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define USB_DESCRIPTORS_LIST_SIZE (sizeof(usb_descriptors_list)/sizeof(usb_descriptors_list[0]))
|
#define USB_DESCRIPTORS_LIST_SIZE (sizeof(usb_descriptors_list)/sizeof(usb_descriptors_list[0]))
|
||||||
|
|
@ -249,7 +297,7 @@ static int usb_as_playback_intf_alt; /* playback streaming interface alternate s
|
||||||
static int as_playback_freq_idx; /* audio playback streaming frequency index (in hw_freq_sampr) */
|
static int as_playback_freq_idx; /* audio playback streaming frequency index (in hw_freq_sampr) */
|
||||||
|
|
||||||
static int out_iso_ep_adr; /* output isochronous endpoint */
|
static int out_iso_ep_adr; /* output isochronous endpoint */
|
||||||
static int in_iso_ep_adr; /* input isochronous endpoint */
|
static int in_iso_feedback_ep_adr; /* input feedback isochronous endpoint */
|
||||||
|
|
||||||
/* small buffer used for control transfers */
|
/* small buffer used for control transfers */
|
||||||
static unsigned char usb_buffer[128] USB_DEVBSS_ATTR;
|
static unsigned char usb_buffer[128] USB_DEVBSS_ATTR;
|
||||||
|
|
@ -314,6 +362,33 @@ bool playback_audio_underflow;
|
||||||
/* usb overflow ? */
|
/* usb overflow ? */
|
||||||
bool usb_rx_overflow;
|
bool usb_rx_overflow;
|
||||||
|
|
||||||
|
/* feedback variables */
|
||||||
|
#define USB_FRAME_MAX 0x7FF
|
||||||
|
#define NR_SAMPLES_HISTORY 32
|
||||||
|
int32_t samples_fb;
|
||||||
|
int32_t buffers_filled_old;
|
||||||
|
long buffers_filled_accumulator;
|
||||||
|
long buffers_filled_accumulator_old;
|
||||||
|
int buffers_filled_avgcount;
|
||||||
|
int buffers_filled_avgcount_old;
|
||||||
|
static uint8_t sendFf[4] USB_DEVBSS_ATTR;
|
||||||
|
static bool sent_fb_this_frame = false;
|
||||||
|
int fb_startframe = 0;
|
||||||
|
bool send_fb = false;
|
||||||
|
|
||||||
|
/* debug screen sample count display variables */
|
||||||
|
static unsigned long samples_received;
|
||||||
|
static unsigned long samples_received_last;
|
||||||
|
int32_t samples_received_report;
|
||||||
|
int buffers_filled_min;
|
||||||
|
int buffers_filled_min_last;
|
||||||
|
int buffers_filled_max;
|
||||||
|
int buffers_filled_max_last;
|
||||||
|
|
||||||
|
/* frame drop recording variables */
|
||||||
|
static int last_frame = 0;
|
||||||
|
static int frames_dropped = 0;
|
||||||
|
|
||||||
/* Schematic view of the RX situation:
|
/* Schematic view of the RX situation:
|
||||||
* (in case NR_BUFFERS = 4)
|
* (in case NR_BUFFERS = 4)
|
||||||
*
|
*
|
||||||
|
|
@ -352,6 +427,37 @@ static unsigned long decode3(uint8_t arr[3])
|
||||||
return arr[0] | (arr[1] << 8) | (arr[2] << 16);
|
return arr[0] | (arr[1] << 8) | (arr[2] << 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// size is samples per frame!
|
||||||
|
static void encodeFBfixedpt(uint8_t arr[4], int32_t value, bool portspeed)
|
||||||
|
{
|
||||||
|
uint32_t fixedpt;
|
||||||
|
// high-speed
|
||||||
|
if (portspeed)
|
||||||
|
{
|
||||||
|
// Q16.16
|
||||||
|
fixedpt = value;
|
||||||
|
|
||||||
|
arr[0] = (fixedpt & 0xFF);
|
||||||
|
arr[1] = (fixedpt>>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)
|
static void set_playback_sampling_frequency(unsigned long f)
|
||||||
{
|
{
|
||||||
// only values 44.1k and higher (array is in descending order)
|
// 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)
|
unsigned long usb_audio_get_playback_sampling_frequency(void)
|
||||||
{
|
{
|
||||||
logf("usbaudio: get playback sampl freq %lu Hz",
|
// logf("usbaudio: get playback sampl freq %lu Hz", hw_freq_sampr[as_playback_freq_idx]);
|
||||||
hw_freq_sampr[as_playback_freq_idx]);
|
|
||||||
return 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;
|
unsigned int i;
|
||||||
/* initialized tSamFreq array */
|
/* initialized tSamFreq array */
|
||||||
logf("usbaudio: supported frequencies");
|
logf("usbaudio: (init) supported frequencies");
|
||||||
// only values 44.1k and higher (array is in descending order)
|
// only values 44.1k and higher (array is in descending order)
|
||||||
for(i = 0; i <= HW_FREQ_44; i++)
|
for(i = 0; i <= HW_FREQ_44; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -393,7 +498,6 @@ void usb_audio_init(void)
|
||||||
|
|
||||||
int usb_audio_request_buf(void)
|
int usb_audio_request_buf(void)
|
||||||
{
|
{
|
||||||
|
|
||||||
// stop playback first thing
|
// stop playback first thing
|
||||||
audio_stop();
|
audio_stop();
|
||||||
|
|
||||||
|
|
@ -439,18 +543,21 @@ int usb_audio_request_endpoints(struct usb_class_driver *drv)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
in_iso_ep_adr = usb_core_request_endpoint(USB_ENDPOINT_XFER_ISOC, USB_DIR_IN, drv);
|
in_iso_feedback_ep_adr = usb_core_request_endpoint(USB_ENDPOINT_XFER_ISOC, USB_DIR_IN, drv);
|
||||||
if(in_iso_ep_adr < 0)
|
if(in_iso_feedback_ep_adr < 0)
|
||||||
{
|
{
|
||||||
usb_core_release_endpoint(out_iso_ep_adr);
|
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;
|
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;
|
as_iso_audio_out_ep.bEndpointAddress = out_iso_ep_adr;
|
||||||
out_iso_ep.bSynchAddress = 0;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -462,7 +569,7 @@ unsigned int usb_audio_get_out_ep(void)
|
||||||
|
|
||||||
unsigned int usb_audio_get_in_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)
|
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 int i;
|
||||||
unsigned char *orig_dest = dest;
|
unsigned char *orig_dest = dest;
|
||||||
|
|
||||||
// logf("get config descriptors");
|
logf("get config descriptors");
|
||||||
|
|
||||||
/** Configuration */
|
/** 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;
|
as_interface_alt_playback.bInterfaceNumber = usb_interface + 1;
|
||||||
|
|
||||||
/* endpoints */
|
/* endpoints */
|
||||||
out_iso_ep.wMaxPacketSize = 1023; /* one micro-frame per transaction */
|
as_iso_audio_out_ep.wMaxPacketSize = 1023;
|
||||||
|
|
||||||
/** Endpoint Interval calculation:
|
/** Endpoint Interval calculation:
|
||||||
* typically sampling frequency is 44100 Hz and top is 192000 Hz, which
|
* typically sampling frequency is 44100 Hz and top is 192000 Hz, which
|
||||||
* account for typical 44100*2(stereo)*2(16-bit) ~= 180 kB/s
|
* 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
|
* 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
|
* for over 1 MB/s.
|
||||||
* 8 = 2^3 micro-frames per frame.
|
|
||||||
* Recall that actual is 2^(bInterval - 1) */
|
* 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 */
|
/** Packing */
|
||||||
for(i = 0; i < USB_DESCRIPTORS_LIST_SIZE; i++)
|
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);
|
*start = rx_buffer + (rx_play_idx * REAL_BUF_SIZE);
|
||||||
*size = rx_buf_size[rx_play_idx];
|
*size = rx_buf_size[rx_play_idx];
|
||||||
rx_play_idx = (rx_play_idx + 1) % NR_BUFFERS;
|
rx_play_idx = (rx_play_idx + 1) % NR_BUFFERS;
|
||||||
|
|
||||||
/* if usb RX buffers had overflowed, we can start to receive again
|
/* if usb RX buffers had overflowed, we can start to receive again
|
||||||
* guard against IRQ to avoid race with completion usb completion (although
|
* guard against IRQ to avoid race with completion usb completion (although
|
||||||
* this function is probably running in IRQ context anyway) */
|
* 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_play_idx = 0;
|
||||||
rx_usb_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
|
// TODO: implement recording from the USB stream
|
||||||
#if (INPUT_SRC_CAPS != 0)
|
#if (INPUT_SRC_CAPS != 0)
|
||||||
audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
|
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)
|
static void usb_audio_stop_playback(void)
|
||||||
{
|
{
|
||||||
// logf("usbaudio: stop playback");
|
logf("usbaudio: stop playback");
|
||||||
if(usb_audio_playing)
|
if(usb_audio_playing)
|
||||||
{
|
{
|
||||||
mixer_channel_stop(PCM_MIXER_CHAN_USBAUDIO);
|
mixer_channel_stop(PCM_MIXER_CHAN_USBAUDIO);
|
||||||
usb_audio_playing = false;
|
usb_audio_playing = false;
|
||||||
}
|
}
|
||||||
|
send_fb = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int usb_audio_set_interface(int intf, int alt)
|
int usb_audio_set_interface(int intf, int alt)
|
||||||
|
|
@ -643,7 +776,17 @@ int usb_audio_get_alt_intf(void)
|
||||||
return usb_as_playback_intf_alt;
|
return usb_as_playback_intf_alt;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool usb_audio_as_playback_endpoint_request(struct usb_ctrlrequest* req, void *reqdata)
|
int32_t usb_audio_get_samplesperframe(void)
|
||||||
|
{
|
||||||
|
return samples_fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 */
|
/* only support sampling frequency */
|
||||||
if(req->wValue != (USB_AS_EP_CS_SAMPLING_FREQ_CTL << 8))
|
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;
|
int ep = req->wIndex & 0xff;
|
||||||
|
|
||||||
if(ep == out_iso_ep_adr)
|
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
|
else
|
||||||
{
|
{
|
||||||
logf("usbaudio: unhandled ep req (ep=%d)", ep);
|
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 !");
|
logf("usbaudio: mute !");
|
||||||
tmp_saved_vol = sound_current(SOUND_VOLUME);
|
tmp_saved_vol = sound_current(SOUND_VOLUME);
|
||||||
// sound_set_volume(sound_min(SOUND_VOLUME));
|
|
||||||
// setvol does range checking for us!
|
// setvol does range checking for us!
|
||||||
global_status.volume = sound_min(SOUND_VOLUME);
|
global_status.volume = sound_min(SOUND_VOLUME);
|
||||||
setvol();
|
setvol();
|
||||||
|
|
@ -736,7 +879,7 @@ static bool feature_unit_set_mute(int value, uint8_t cmd)
|
||||||
else if(value == 0)
|
else if(value == 0)
|
||||||
{
|
{
|
||||||
logf("usbaudio: not muted !");
|
logf("usbaudio: not muted !");
|
||||||
// sound_set_volume(tmp_saved_vol);
|
|
||||||
// setvol does range checking for us!
|
// setvol does range checking for us!
|
||||||
global_status.volume = tmp_saved_vol;
|
global_status.volume = tmp_saved_vol;
|
||||||
setvol();
|
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)));
|
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!
|
// setvol does range checking for us!
|
||||||
// we cannot guarantee the host will send us a volume within our range
|
// 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));
|
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;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -856,7 +998,7 @@ static bool usb_audio_set_get_feature_unit(struct usb_ctrlrequest* req, void *re
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
/* selectors */
|
/* 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)
|
if(req->bRequest & USB_AC_GET_REQ)
|
||||||
{
|
{
|
||||||
/* get */
|
/* 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) reqdata;
|
||||||
|
(void) dest;
|
||||||
|
|
||||||
switch(req->bRequestType & USB_RECIP_MASK)
|
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 */
|
/* determine if enough prebuffering has been done to restart audio */
|
||||||
bool prebuffering_done(void)
|
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;
|
int diff = (rx_usb_idx - rx_play_idx + NR_BUFFERS) % NR_BUFFERS;
|
||||||
return diff >= MINIMUM_BUFFERS_QUEUED;
|
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;
|
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)
|
bool usb_audio_get_underflow(void)
|
||||||
{
|
{
|
||||||
return playback_audio_underflow;
|
return playback_audio_underflow;
|
||||||
|
|
@ -1054,6 +1219,11 @@ bool usb_audio_get_overflow(void)
|
||||||
return usb_rx_overflow;
|
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)
|
void usb_audio_transfer_complete(int ep, int dir, int status, int length)
|
||||||
{
|
{
|
||||||
/* normal handler is too slow to handle the completion rate, because
|
/* 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)
|
bool usb_audio_fast_transfer_complete(int ep, int dir, int status, int length)
|
||||||
{
|
{
|
||||||
(void) dir;
|
(void) dir;
|
||||||
|
bool retval = false;
|
||||||
|
|
||||||
if(ep == out_iso_ep_adr && usb_as_playback_intf_alt == 1)
|
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)
|
if(status != 0)
|
||||||
return true; /* FIXME how to handle error here ? */
|
return true; /* FIXME how to handle error here ? */
|
||||||
/* store length, queue buffer */
|
/* store length, queue buffer */
|
||||||
rx_buf_size[rx_usb_idx] = length;
|
rx_buf_size[rx_usb_idx] = length;
|
||||||
rx_usb_idx = (rx_usb_idx + 1) % NR_BUFFERS;
|
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 */
|
/* guard against IRQ to avoid race with completion audio completion */
|
||||||
int oldlevel = disable_irq_save();
|
int oldlevel = disable_irq_save();
|
||||||
/* setup a new transaction except if we ran out of buffers */
|
/* 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);
|
mixer_channel_play_data(PCM_MIXER_CHAN_USBAUDIO, playback_audio_get_more, NULL, 0);
|
||||||
}
|
}
|
||||||
restore_irq(oldlevel);
|
restore_irq(oldlevel);
|
||||||
return true;
|
retval = true;
|
||||||
}
|
}
|
||||||
else
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,14 @@
|
||||||
|
|
||||||
#include "usb_ch9.h"
|
#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():
|
* usb_audio_request_endpoints():
|
||||||
*
|
*
|
||||||
|
|
@ -178,6 +186,28 @@ int usb_audio_get_main_intf(void);
|
||||||
*/
|
*/
|
||||||
int usb_audio_get_alt_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():
|
* usb_audio_get_out_ep():
|
||||||
*
|
*
|
||||||
|
|
@ -199,6 +229,25 @@ unsigned int usb_audio_get_in_ep(void);
|
||||||
*/
|
*/
|
||||||
int usb_audio_get_prebuffering(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():
|
* usb_audio_get_underflow():
|
||||||
*
|
*
|
||||||
|
|
@ -213,6 +262,13 @@ bool usb_audio_get_underflow(void);
|
||||||
*/
|
*/
|
||||||
bool usb_audio_get_overflow(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():
|
* usb_audio_get_cur_volume():
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,14 @@
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
/* Parts of this file are based on Frank Gevaerts work and on audio.h from linux */
|
/* 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
|
#ifndef USB_AUDIO_DEF_H
|
||||||
#define 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_SAMPLING_FREQ_CTL 0x01
|
||||||
#define USB_AS_EP_CS_PITCH_CTL 0x02
|
#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 bLength;
|
||||||
uint8_t bDescriptorType;
|
uint8_t bDescriptorType;
|
||||||
|
|
||||||
uint8_t bEndpointAddress;
|
uint8_t bEndpointAddress;
|
||||||
uint8_t bmAttributes;
|
uint8_t bmAttributes;
|
||||||
uint16_t wMaxPacketSize;
|
uint16_t wMaxPacketSize;
|
||||||
uint8_t bInterval;
|
uint8_t bInterval; /* MUST be set to 1 (full speed), +3 for high-speed? */
|
||||||
uint8_t bRefresh;
|
uint8_t bRefresh; /* USB Audio Class 1.0 specific: set to 0 */
|
||||||
uint8_t bSynchAddress;
|
uint8_t bSynchAddress; /* USB Audio Class 1.0 specific: used to identify synch ep */
|
||||||
} __attribute__ ((packed));
|
} __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 bLength;
|
||||||
uint8_t bDescriptorType; /* USB_DT_CS_ENDPOINT */
|
uint8_t bDescriptorType; /* USB_DT_CS_ENDPOINT */
|
||||||
uint8_t bDescriptorSubType; /* USB_AS_EP_GENERAL */
|
uint8_t bDescriptorSubType; /* USB_AS_EP_GENERAL */
|
||||||
uint8_t bmAttributes;
|
uint8_t bmAttributes;
|
||||||
uint8_t bLockDelayUnits;
|
uint8_t bLockDelayUnits; /* USB Audio Class 1.0 specific */
|
||||||
uint16_t wLockDelay;
|
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));
|
} __attribute__ ((packed));
|
||||||
|
|
||||||
#define USB_AS_FORMAT_TYPE_I_UNDEFINED 0x0
|
#define USB_AS_FORMAT_TYPE_I_UNDEFINED 0x0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue