USB AudioStreaming Isochronous Feedback Calculations

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

USB AudioStreaming Isochronous Feedback Calculations

Jump to solution
1,608 Views
cdsteinkuehler
Contributor III

I am attempting to understand the USB feedback calculations used in the various asynchronous isochronous AudioStreaming sink examples (eg: audio_speaker and composite_hid_audio_unified).  The code for all the examples appears to be essentially identical (USB_DeviceCalculateFeedback()), but I am either not correctly understanding it's operation or it seems to have a few fundamental flaws.

First, it appears the feedback is calculated based on the total amount of data send to the DAC (audioSendCount) and the total amount of data received via USB (totalFrameValue), with both values zeroed at the "start" of audio streaming.  This basic approach seems to have at least two serious drawbacks:

  1. The calculated feedback value is a long-term average of the ratio between the two clock domains, and does not accurately represent the current clock ratio.  If both clocks are perfectly stable, this would work fine, but if the clocks are drifting at all this long term average will not update properly.
  2. The feedback calculations eventually roll over or "wrap".  This will only take about 6 hours for a 16-bit stereo 48 Khz samples (audioSendCount is only 32 bits), and can happen much faster with more channels, higher sample rates, and 24 or 32 bit samples.

If I understand the USB specifications correctly, the feedback value should be the actual measured local sample rate relative to the USB notion of time, i.e., the USB (micro)frame frequency.  I do not understand why this isn't simply measured using one of the timers (eg: SCTimer clocked by the audio MCLK and using USBx_FRAME_TOGGLE as a capture trigger or similar).

0 Kudos
1 Solution
1,548 Views
cdsteinkuehler
Contributor III

I updated the code and use the SCTimer clocked by the 24.576 MHz audio clock and the USB1_FRAME_TOGGLE as a capture event to directly measure the sample frequency vs. the USB SOF timebase.  Most of the logic in the USB_DeviceCalculateFeedback() routine is disabled, with just the bit to adjust the feedback value if the buffer starts to over/under-flow left.

View solution in original post

0 Kudos
4 Replies
1,598 Views
cdsteinkuehler
Contributor III

I ran a long term test over night and as expected, the feedback calculation got confused when the audioSendCount wrapped, causing the host to send data too fast, eventually overrunning the audio buffers and causing the stream to reset.

Also, related to issue #1, above: Since the feedback logic is tracking the long-term average rate and not the current actual sample rate, if the short-term frequency ever drifts more than AUDIO_ADJUST_MIN_STEP, the local buffers will either empty or overrun as the wrong feedback value is being communicated to the host.  In practice, this typically shows up as no corrections needed at the start of the stream, followed by a constant need to apply the correction (vs. simply reporting the correct sample frequency) to maintain the proper audio buffer level.

0 Kudos
1,549 Views
cdsteinkuehler
Contributor III

I updated the code and use the SCTimer clocked by the 24.576 MHz audio clock and the USB1_FRAME_TOGGLE as a capture event to directly measure the sample frequency vs. the USB SOF timebase.  Most of the logic in the USB_DeviceCalculateFeedback() routine is disabled, with just the bit to adjust the feedback value if the buffer starts to over/under-flow left.

0 Kudos
1,583 Views
xiangjun_rong
NXP TechSupport
NXP TechSupport

Hi, Charles,

Do you mean the function?

#if (defined(USB_DEVICE_CONFIG_LPCIP3511FS) && (USB_DEVICE_CONFIG_LPCIP3511FS > 0U))
static int32_t audioSpeakerUsedDiff = 0x0, audioSpeakerDiffThres = 0x0;
static uint32_t audioSpeakerUsedSpace = 0x0, audioSpeakerLastUsedSpace = 0x0;
void USB_AudioFSSync()
{
if (g_UsbDeviceAudioSpeaker.speakerIntervalCount != AUDIO_CALCULATE_Ff_INTERVAL)
{
g_UsbDeviceAudioSpeaker.speakerIntervalCount++;
return;
}
g_UsbDeviceAudioSpeaker.speakerIntervalCount = 1;
g_UsbDeviceAudioSpeaker.timesFeedbackCalculate++;
if (g_UsbDeviceAudioSpeaker.timesFeedbackCalculate == 2)
{
audioSpeakerUsedSpace = USB_AudioSpeakerBufferSpaceUsed();
audioSpeakerLastUsedSpace = audioSpeakerUsedSpace;
}

if (g_UsbDeviceAudioSpeaker.timesFeedbackCalculate > 2)
{
audioSpeakerUsedSpace = USB_AudioSpeakerBufferSpaceUsed();
audioSpeakerUsedDiff += (audioSpeakerUsedSpace - audioSpeakerLastUsedSpace);
audioSpeakerLastUsedSpace = audioSpeakerUsedSpace;

if ((audioSpeakerUsedDiff > -AUDIO_SAMPLING_RATE_KHZ) && (audioSpeakerUsedDiff < AUDIO_SAMPLING_RATE_KHZ))
{
audioSpeakerDiffThres = 4 * AUDIO_SAMPLING_RATE_KHZ;
}
if (audioSpeakerUsedDiff <= -audioSpeakerDiffThres)
{
audioSpeakerDiffThres += 4 * AUDIO_SAMPLING_RATE_KHZ;
audio_trim_down();
}
if (audioSpeakerUsedDiff >= audioSpeakerDiffThres)
{
audioSpeakerDiffThres += 4 * AUDIO_SAMPLING_RATE_KHZ;
audio_trim_up();
}
}
}
#endif

I use SDK2.7.0_LPCXpresso54828 paclkage, the example is _audio_speaker_bm.

BR

XiangJun Rong

0 Kudos
1,573 Views
cdsteinkuehler
Contributor III

No, that is not the same as the USB_DeviceCalculateFeedback() function found in the SDK_2.x_LPCXpresso55S16 version 2.9.0 in either the audio_speaker or composite_hid_audio_unified examples.

I have installed the LPC54628 SDK (I assume the 54828 is a typo) and reviewed the speaker and unified example designs.  It appears these examples do not support asynchronous playback so do not include the USB_DeviceCalculateFeedback() function.  Instead, the PLL used to generate the audio timings is adjusted to remain synchronized with the USB bus via adjusting the SYSCON->FROCTRL register (in full speed mode via the USB_AudioFSSync() routine you listed previously) or the SYSCON->AUDPLLFRAC register (in high-speed mode via the SCTIMER_SOF_TOGGLE_HANDLER() routine).

I need to use the asynchronous mode as my audio clock is not locked to the USB bus, so the LPC54628 examples are not applicable.

0 Kudos