1
\$\begingroup\$

Hopefully this is the right sub to ask this question. Anyways, I'm working on a USB Audio device on a microcontroller and I'm having issues with getting a consistent audio input.

Some preface:

As of right now, I have connected the mcu and codec via I2S (codec is slave) with the following configurations:

MCLK = 11.2896 MHz
BCLK = 2.8224 MHz
LRCLK = 44.1 kHz
USB CLK = 12 MHz

When I do a simple DSP bypass (codec input -> mcu -> codec output), the audio is glitch free and awesome. However, when I enable USB, that's when things get weird. I'll get occasional glitches and the signal sometimes repeats itself. I'm not totally sure if this is because I'm not using enough buffers when writing to USB.

Right now my codec reads and USB writes are happening separately:

Codec Flow: Codec Input -> MCU I2S RX -> DMA Interrupt when I2S RX buffer is full 
            -> Write this buffer into USB buffers -> Calculate next input frame len

USB: USB initiates input -> Write buffer to USB endpoint -> USB Write Callback 
     called when USB transfer is complete -> Start reading next buffer

How I'm calculating my frame lengths:

#define AUDIO_POLL_INT     4
#define FRAME_BYTES        3
#define NUM_CHANNELS       STEREO

// Calculate the frame length
uint16_t frame_len = 44 (44.1kHz/1000 samples) * NUM_CHANNELS * FRAME_BYTES;
// Every 10 ms, calculate frame length with additional frame (only applies to 44.1kHz sample rate)
if (!(frame_pos % 9)) frame_len += (1 * NUM_CHANNELS * FRAME_BYTES)

// Increment frame position per usb call
frame_pos = (((frame_pos + 1) / 8) * (2 << (AUDIO_POLL_INT-1))) % 10;
frame_pos = (frame_pos + 1) % 10;

Actual Question/What I think is going wrong (?)

I'm guessing there is some jitter issues due to the codec MCLK and USB CLK rates being different (11.2896 vs 12). I'm only guessing this because the interruptions/glitches happen pretty periodically. I also have noticed that the codec buffers will "catch" up to the USB buffers every once in a while (codec buffer will overwrite a USB buffer even though the USB buffer is marked "full"). However, I'm not sure how to stop this from happening because the codec would go out of sync and I would lose audio data. I also cannot speed up the USB transfers obviously... I'm transferring a max of 270 bytes per USB write for a sample rate of 44.1 kHz.

If anyone has had experience with USB audio and synchronization, your help would be appreciated! I'm not sure how to approach the problem from here honestly. If you're wondering, USB Audio output is fine though (with the exception of some channels not being synchronized).

\$\endgroup\$
  • \$\begingroup\$ How accurate is your 11.whatever MCLK? How is it generated? \$\endgroup\$ – pjc50 Jul 14 '17 at 21:47
  • \$\begingroup\$ @pjc50 Pretty accurate because it's generated by an external crystal \$\endgroup\$ – yun Jul 14 '17 at 22:18
  • \$\begingroup\$ What method of USB driver is what communicating with what? Is this a micro connected to a PC? What driver and mode is it using? \$\endgroup\$ – rdtsc Jul 14 '17 at 22:58
  • \$\begingroup\$ @rdtsc I'm using USB Audio 2.0 and my micro is communicating with a Mac. Using asynchronous mode for my USB Audio input. \$\endgroup\$ – yun Jul 14 '17 at 23:09
  • \$\begingroup\$ "Man with one watch knows what time it is, man with two watches is never sure" => the two crystals sound like a problem. \$\endgroup\$ – pjc50 Jul 15 '17 at 7:57
3
\$\begingroup\$

It's hard to be sure (since this is obviously not your actual code), but it appears that you have hard-coded the pattern of frame lengths on the USB side, on the assumption that the audio sample rate (i.e., the 11.2896 MHz codec clock) is strictly locked to the USB data rate (i.e., the 12.000 MHz USB clock). This would only be true if the codec clock was driven by a PLL that is referenced to the USB clock. In actual fact, they can be different by 10s or even 100s of ppm, given the usual tolerances associated with general-purpose crystals.

Instead, you should be picking the USB frame length dynamically, based on the actual amount of data you have in local buffers that you have received from the codec. If this total is above some threshold, you should send the longer USB frame; otherwise, send the shorter USB frame. This allows the average rate of the USB transfers to track the actual sample rate defined by the codec clock.

\$\endgroup\$
  • \$\begingroup\$ Ah I see, so if it was a PLL then I could hard code these frame lengths. I was led to believe it would just work from several resources. Essentially I need to keep track of the time of both the USB and codec so I can calculate the true "frame length"? Ugh sounds like a lot more code I need to do haha. Thanks! \$\endgroup\$ – yun Jul 14 '17 at 23:08
  • \$\begingroup\$ You can't mock with USB frame length, it has strict limits. USB audio is one ugly area of the bus. USB 2.0 specifications have about 20 pages (!!!) of write-up on the subject of USB clock synchronization and buffering for rate matching, see Section 5.12. \$\endgroup\$ – Ale..chenski Jul 15 '17 at 2:05
  • \$\begingroup\$ @AliChen: The OP is already sending two different frame lengths, but on a fixed schedule. I'm just saying that the schedule needs to be dynamic -- but still using the same two frame lengths. Sorry if that wasn't clear! \$\endgroup\$ – Dave Tweed Jul 15 '17 at 2:19
  • \$\begingroup\$ @AliChen I should definitely read the USB documentation more thoroughly about this but yes I am sending these frame lengths at a fixed schedule right now. I think Dave is right about it needing to be dynamic so I will try this out next week! \$\endgroup\$ – yun Jul 15 '17 at 3:14
  • \$\begingroup\$ The whole point of the asynchronous transfer mode is that the sample clock (derived from your MCLK) is not synchronized to the USB clock. \$\endgroup\$ – CL. Jul 15 '17 at 6:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.