Merge pull request #3321 from gabChouin/audio_ep_in_target_fifo_size

audio: Implement support for variable EP in flow control fifo level
This commit is contained in:
Zixun LI
2025-11-07 07:15:15 -08:00
committed by GitHub
3 changed files with 66 additions and 46 deletions

View File

@ -35,13 +35,6 @@
// MACRO CONSTANT TYPEDEF PROTOTYPES
//--------------------------------------------------------------------+
// List of supported sample rates for UAC2
const uint32_t sample_rates[] = {44100, 48000, 88200, 96000};
#define N_SAMPLE_RATES TU_ARRAY_SIZE(sample_rates)
uint32_t current_sample_rate = 44100;
/* Blink pattern
* - 25 ms : streaming data
* - 250 ms : device not mounted
@ -76,6 +69,7 @@ static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
// Current states
uint8_t mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1]; // +1 for master channel 0
int16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1];// +1 for master channel 0
uint32_t current_sample_rate = 44100;
// Buffer for speaker data
uint16_t i2s_dummy_buffer[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ / 2];
@ -316,6 +310,10 @@ static bool audio10_get_req_entity(uint8_t rhport, tusb_control_request_t const
//--------------------------------------------------------------------+
#if TUD_OPT_HIGH_SPEED
// List of supported sample rates for UAC2
const uint32_t sample_rates[] = {44100, 48000, 88200, 96000};
#define N_SAMPLE_RATES TU_ARRAY_SIZE(sample_rates)
static bool audio20_clock_get_request(uint8_t rhport, audio20_control_request_t const *request) {
TU_ASSERT(request->bEntityID == UAC2_ENTITY_CLOCK);
@ -548,6 +546,17 @@ void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedba
// Set feedback method to fifo counting
feedback_param->method = AUDIO_FEEDBACK_METHOD_FIFO_COUNT;
feedback_param->sample_freq = current_sample_rate;
// About FIFO threshold:
//
// By default the threshold is set to half FIFO size, which works well in most cases,
// you can reduce the threshold to have less latency.
//
// For example, here we could set the threshold to 2 ms of audio data, as audio_task() read audio data every 1 ms,
// having 2 ms threshold allows some margin and a quick response:
//
// feedback_param->fifo_count.fifo_threshold =
// current_sample_rate * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX / 1000 * 2;
}
#if CFG_AUDIO_DEBUG

View File

@ -217,6 +217,7 @@ typedef struct
uint16_t ep_in_sz; // Current size of TX EP
uint8_t ep_in_as_intf_num;// Corresponding Standard AS Interface Descriptor (4.9.1) belonging to output terminal to which this EP belongs - 0 is invalid (this fits to UAC2 specification since AS interfaces can not have interface number equal to zero)
uint8_t ep_in_alt; // Current alternate setting of TX EP
uint16_t ep_in_fifo_threshold;// Target size for the EP IN FIFO.
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_OUT
@ -269,10 +270,6 @@ typedef struct
uint16_t packet_sz_tx[3];
uint8_t bclock_id_tx;
uint8_t interval_tx;
#endif
// Encoding parameters - parameters are set when alternate AS interface is set by host
#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL
uint8_t format_type_tx;
uint8_t n_channels_tx;
uint8_t n_bytes_per_sample_tx;
@ -455,7 +452,7 @@ static inline uint8_t audiod_get_audio_fct_idx(audiod_function_t *audio);
#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL
static void audiod_parse_flow_control_params(audiod_function_t *audio, uint8_t const *p_desc);
static bool audiod_calc_tx_packet_sz(audiod_function_t *audio);
static uint16_t audiod_tx_packet_size(const uint16_t *norminal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t max_size);
static uint16_t audiod_tx_packet_size(const uint16_t *nominal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t fifo_threshold, uint16_t max_size);
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
@ -549,6 +546,17 @@ tu_fifo_t *tud_audio_n_get_ep_in_ff(uint8_t func_id) {
return NULL;
}
uint16_t tud_audio_n_get_ep_in_fifo_threshold(uint8_t func_id) {
if (func_id < CFG_TUD_AUDIO) return _audiod_fct[func_id].ep_in_fifo_threshold;
return 0;
}
void tud_audio_n_set_ep_in_fifo_threshold(uint8_t func_id, uint16_t threshold) {
if (func_id < CFG_TUD_AUDIO && threshold < _audiod_fct[func_id].ep_in_ff.depth) {
_audiod_fct[func_id].ep_in_fifo_threshold = threshold;
}
}
static bool audiod_tx_xfer_isr(uint8_t rhport, audiod_function_t * audio, uint16_t n_bytes_sent) {
uint8_t idx_audio_fct = audiod_get_audio_fct_idx(audio);
@ -560,7 +568,7 @@ static bool audiod_tx_xfer_isr(uint8_t rhport, audiod_function_t * audio, uint16
#if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL
// packet_sz_tx is based on total packet size, here we want size for each support buffer.
n_bytes_tx = audiod_tx_packet_size(audio->packet_sz_tx, tu_fifo_count(&audio->ep_in_ff), audio->ep_in_ff.depth, audio->ep_in_sz);
n_bytes_tx = audiod_tx_packet_size(audio->packet_sz_tx, tu_fifo_count(&audio->ep_in_ff), audio->ep_in_ff.depth, audio->ep_in_fifo_threshold, audio->ep_in_sz);
#else
n_bytes_tx = tu_min16(tu_fifo_count(&audio->ep_in_ff), audio->ep_in_sz);// Limit up to max packet size, more can not be done for ISO
#endif
@ -630,19 +638,6 @@ static inline bool audiod_fb_send(uint8_t func_id) {
*audio->fb_buf = audio->feedback.value;
}
// About feedback format on FS
//
// 3 variables: Format | packetSize | sendSize | Working OS:
// 16.16 4 4 Linux, Windows
// 16.16 4 3 Linux
// 16.16 3 4 Linux
// 16.16 3 3 Linux
// 10.14 4 4 Linux
// 10.14 4 3 Linux
// 10.14 3 4 Linux, OSX
// 10.14 3 3 Linux, OSX
//
// We send 3 bytes since sending packet larger than wMaxPacketSize is pretty ugly
return usbd_edpt_xfer(audio->rhport, audio->ep_fb, (uint8_t *) audio->fb_buf, uac_version == 1 ? 3 : 4);
}
@ -1197,6 +1192,8 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p
audio->ep_in_as_intf_num = itf;
audio->ep_in_alt = alt;
audio->ep_in_sz = tu_edpt_packet_size(desc_ep);
// Set the default EP IN FIFO threshold to half fifo depth.
audio->ep_in_fifo_threshold = audio->ep_in_ff.depth / 2;
// If flow control is enabled, parse for the corresponding parameters - doing this here means only AS interfaces with EPs get scanned for parameters
#if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL
@ -1549,7 +1546,7 @@ static bool audiod_fb_params_prepare(uint8_t func_id, uint8_t alt) {
// Prepare feedback computation if endpoint is available
if (audio->ep_fb != 0) {
audio_feedback_params_t fb_param;
audio_feedback_params_t fb_param = {0};
tud_audio_feedback_params_cb(func_id, alt, &fb_param);
audio->feedback.compute_method = fb_param.method;
@ -1590,15 +1587,15 @@ static bool audiod_fb_params_prepare(uint8_t func_id, uint8_t alt) {
} break;
case AUDIO_FEEDBACK_METHOD_FIFO_COUNT: {
// Initialize the threshold level to half filled
uint16_t fifo_lvl_thr = tu_fifo_depth(&audio->ep_out_ff) / 2;
audio->feedback.compute.fifo_count.fifo_lvl_thr = fifo_lvl_thr;
audio->feedback.compute.fifo_count.fifo_lvl_avg = ((uint32_t) fifo_lvl_thr) << 16;
// Determine FIFO threshold
uint16_t fifo_threshold = fb_param.fifo_count.fifo_threshold ? fb_param.fifo_count.fifo_threshold : tu_fifo_depth(&audio->ep_out_ff) / 2;
audio->feedback.compute.fifo_count.fifo_lvl_thr = fifo_threshold;
audio->feedback.compute.fifo_count.fifo_lvl_avg = ((uint32_t) fifo_threshold) << 16;
// Avoid 64bit division
uint32_t nominal = ((fb_param.sample_freq / 100) << 16) / (frame_div / 100);
audio->feedback.compute.fifo_count.nom_value = nominal;
audio->feedback.compute.fifo_count.rate_const[0] = (uint16_t) ((audio->feedback.max_value - nominal) / fifo_lvl_thr);
audio->feedback.compute.fifo_count.rate_const[1] = (uint16_t) ((nominal - audio->feedback.min_value) / fifo_lvl_thr);
audio->feedback.compute.fifo_count.rate_const[0] = (uint16_t) ((audio->feedback.max_value - nominal) / fifo_threshold);
audio->feedback.compute.fifo_count.rate_const[1] = (uint16_t) ((nominal - audio->feedback.min_value) / fifo_threshold);
// On HS feedback is more sensitive since packet size can vary every MSOF, could cause instability
if (tud_speed_get() == TUSB_SPEED_HIGH) {
audio->feedback.compute.fifo_count.rate_const[0] /= 8;
@ -1861,22 +1858,22 @@ static bool audiod_calc_tx_packet_sz(audiod_function_t *audio) {
return true;
}
static uint16_t audiod_tx_packet_size(const uint16_t *norminal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t max_depth) {
static uint16_t audiod_tx_packet_size(const uint16_t *nominal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t fifo_threshold, uint16_t max_depth) {
// Flow control need a FIFO size of at least 4*Navg
if (norminal_size[1] && norminal_size[1] <= fifo_depth * 4) {
if (nominal_size[1] && nominal_size[1] <= fifo_depth * 4) {
// Use blackout to prioritize normal size packet
static int ctrl_blackout = 0;
uint16_t packet_size;
uint16_t slot_size = norminal_size[2] - norminal_size[1];
if (data_count < norminal_size[0]) {
uint16_t slot_size = nominal_size[2] - nominal_size[1];
if (data_count < nominal_size[0]) {
// If you get here frequently, then your I2S clock deviation is too big !
packet_size = 0;
} else if (data_count < fifo_depth / 2 - slot_size && !ctrl_blackout) {
packet_size = norminal_size[0];
} else if (data_count < (fifo_threshold - slot_size) && !ctrl_blackout) {
packet_size = nominal_size[0];
ctrl_blackout = 10;
} else if (data_count > fifo_depth / 2 + slot_size && !ctrl_blackout) {
packet_size = norminal_size[2];
if (norminal_size[0] == norminal_size[1]) {
} else if (data_count > (fifo_threshold + slot_size) && !ctrl_blackout) {
packet_size = nominal_size[2];
if (nominal_size[0] == nominal_size[1]) {
// nav > INT(nav), eg. 44.1k, 88.2k
ctrl_blackout = 0;
} else {
@ -1884,7 +1881,7 @@ static uint16_t audiod_tx_packet_size(const uint16_t *norminal_size, uint16_t da
ctrl_blackout = 10;
}
} else {
packet_size = norminal_size[1];
packet_size = nominal_size[1];
if (ctrl_blackout) {
ctrl_blackout--;
}

View File

@ -188,6 +188,8 @@ tu_fifo_t* tud_audio_n_get_ep_out_ff (uint8_t func_id);
uint16_t tud_audio_n_write (uint8_t func_id, const void * data, uint16_t len);
bool tud_audio_n_clear_ep_in_ff (uint8_t func_id);
tu_fifo_t* tud_audio_n_get_ep_in_ff (uint8_t func_id);
uint16_t tud_audio_n_get_ep_in_fifo_threshold(uint8_t func_id);
void tud_audio_n_set_ep_in_fifo_threshold(uint8_t func_id, uint16_t threshold);
#endif
#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP
@ -331,10 +333,12 @@ typedef struct {
union {
struct {
uint32_t mclk_freq; // Main clock frequency in Hz i.e. master clock to which sample clock is based on
}frequency;
} frequency;
struct {
uint16_t fifo_threshold; // Target FIFO threshold level, default to half FIFO if not set
} fifo_count;
};
}audio_feedback_params_t;
} audio_feedback_params_t;
// Invoked when needed to set feedback parameters
void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t* feedback_param);
@ -424,6 +428,16 @@ TU_ATTR_ALWAYS_INLINE static inline tu_fifo_t* tud_audio_get_ep_in_ff(void) {
return tud_audio_n_get_ep_in_ff(0);
}
TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_get_ep_in_fifo_threshold(void)
{
return tud_audio_n_get_ep_in_fifo_threshold(0);
}
TU_ATTR_ALWAYS_INLINE static inline void tud_audio_set_ep_in_fifo_threshold(uint16_t threshold)
{
tud_audio_n_set_ep_in_fifo_threshold(0, threshold);
}
#endif
#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP