Distorted audio when using USB input on Teensy 3.1

Status
Not open for further replies.

donturner

Member
I have a Teensy 3.1 and Audio Shield Rev B which I have configured to be a USB audio device. My problem is that the audio from the computer becomes distorted after 2 minutes.

Here's some recordings of the issue

Music: https://drive.google.com/file/d/0B89PTFSyNDGZTnd4cHpmUWVvV0U/view?usp=sharing
Sine wave: https://drive.google.com/file/d/0B89PTFSyNDGZT0JGdjBtSnlFOUU/view?usp=sharing

You can see the issue visually here from the sine wave distortion. It looks like the teensy is getting underruns and outputting zeros instead of the audio data:

Screen Shot 2016-06-11 at 10.04.33.png

Code:
#include <Audio.h>
 
AudioInputUSB            usb1;           //xy=200,69
AudioOutputI2S           i2s1;           //xy=365,94
AudioConnection          patchCord1(usb1, 0, i2s1, 0);
AudioConnection          patchCord2(usb1, 1, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=302,184

void setup() {    
  AudioMemory(12);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.6);
}

void loop() {
}

I thought originally that this was a problem with the audio shield, however, I have subsequently connected a piezo buzzer to the DAC pin on the Teensy and I can hear the same distortion. I have also eliminated a fault with my Teensy by testing using a second identical Teensy and audio shield. It exhibits the same issue.

My suspicion is that there's a bug in the following file: https://github.com/PaulStoffregen/cores/blob/master/teensy3/usb_audio.cpp

Obviously I would like to fix this issue and am keen to help with the solution. I am a software programmer with some familiarity with audio systems, however, I don't have much experience with Arduino (yet). Some questions (sorry if this is noob stuff):

- Any tips on the best way to debug an issue like this?
- I've swapped the Teensy cores library for my forked one on github so I can make changes, however, I haven't been able to enable the serial output from the cores library. The serial_print() method, example here, doesn't seem to have any effect. How can I get that output?
- What is the best IDE to use for debugging/development? The Arduino one is so basic (no code completion or 'jump to method declaration' functionality).

Github issue on the cores library: https://github.com/PaulStoffregen/cores/issues/134
 
It might be, too, that the computer produces these drops already into the USB audio stream, especially if you have a weak processor, low ram, or a heavy background load. I'd do a differential diagnose with an USB audio headphone or another USB to audio converter first, to make sure that the USB data from the computer is ok.
 
Thanks yes, good point. I have tested with a NI Komplete Audio 6 USB sound interface and the problem doesn't occur. My computer is a Macbook Pro with 2.2GHz i7 CPU with 16GB RAM.
 
I wonder why it happens after 2 minutes and not all the time (?)
Are there other devices connected to the same USB ? Do you use a Hub ?
 
I have run the same test using a Windows 7 machine using Chrome and Soundcloud for about 15 minutes: the issue did not occur.

On my mac I am using a direct cable connection to one of the USB ports, no hub. The only other USB device I have plugged in is a Yubikey security device.

It seems like @ftrias has encountered the same problem in this thread: https://forum.pjrc.com/threads/24309-USB-Audio-for-Teensy-3-0?p=102968&viewfull=1#post102968. The fix/workaround was to remove SYNC_ENDPOINT from teensy3/usb_desc.c however I don't know whether this has any other side effects.
 
I have tried removing SYNC_ENDPOINT: https://github.com/dturner/cores/commit/2ba638a3f791b26953f4835ff7e3b8d54ca129e8.

The result is that I no longer hear distortion which gets progressively worse after 2 minutes. However, I can still hear infrequent (like every 10s) audio glitches/dropouts so this should be considered a workaround rather than a fix.

I would still really like to find out how I can monitor the debug output from usb_audio.cpp here: https://github.com/PaulStoffregen/cores/blob/master/teensy3/usb_audio.cpp#L202 so I can debug this further. Any hints?
 
I'm pretty sure the problem is OS-X is ignoring the sync endpoint, and trying to do some sort of implicit sample rate detection based on the incoming data.

Removing the sync endpoint is *not* the proper solution. This absolutely does break things for Linux and Windows.
 
Thanks Paul, and apologies for double posting, just wanted to make sure that the issue was filed in the right place.

How can I enable the debug serial output in usb_audio.cpp?
 
How can I enable the debug serial output in usb_audio.cpp?

Add Serial1.begin(115200) in setup() and uncomment some of those lines. Connect a USB-serial cable to TX1. Be careful not to print too much stuff, since the USB packets happen 1000 times per second.
 
That cable seemed good - you can get a 3.3V FTDI adapter from SparkFun or perhaps Adafruit for more like $14.

If you have a second Teensy you can use that to take in the serial and print it out to the USB port for debugging. A second Teensy - a T_LC will do - would cost less and serve the same purpose - or better. It takes a small sketch - but very trivial and easy to make or modify from a sample.
 
Whilst I'm waiting for a USB serial cable I tried debugging usb_audio.cpp by using Serial.print to write to the default serial interface. I found that I'm getting quite a few buffer underruns (indicated by '%'s) in usb_audio_transmit_callback.

Could this be the cause of the issue, or is this just a side effect of logging within this function? Any info you can give on how the usb_audio.cpp class works would be appreciated, I'm getting a bit 'lost in the weeds'.
 
I have a couple of different FTDI cables sitting around, as I have needed them with other boards. However you need to get one that has 3.3v IO pins, like:
https://www.sparkfun.com/products/9717
or probably easier, https://www.sparkfun.com/products/12977

As defragster mentioned if you have another Teensy sitting around simply connect the two of them to each other with maybe pin0 of one going to pin 1 of the other, and 1 to 0...

Then run simple sketch on the other Teensy. Something like:
Code:
void setup() {
    Serial1.begin(115200);
    Serial.begin (115200);
}

void loop() {
    int ch;
    while ((ch = Serial1.read()) != -1) {
        Serial.write(ch);
    }
    // optional if you wish to go the other way
    while ((ch = Serial.read()) != -1) {
        Serial1.write(ch);
    }
}
Warning: typed in on the fly, so could be issues...
 
Yeah, all correct, but the cable has the plus, that you don't need to re-compile code for "the other" teeny to set other speeds...
 
Last edited:
Are you using Mac, Windows or Linux.

There's a known problem on Macs where OSX doesn't seem to be using the explicit rate feedback endpoint. I don't understand why...
 
Thanks for all the advice. As it happens I do have a few other Teensys around so I'll try that before splashing out on a cable.

Paul - I'm using Mac OSX 10.11.5 El Capitan. I have tested on Windows and the problem does not occur so it's definitely a Mac issue. That said, I don't have issues with other USB sound cards so I'm guessing they use a different algorithm for receiving audio data.
 
Apologies for length, this is a complicated problem to debug (for me anyway). I think I'm a bit closer to figuring it out but could do with some expert input.

Firstly, here's my understanding of the way the Teensy operates as a USB audio output device.

All communication is done through 2 USB endpoints defined by the Teensy:

#1 An isochronous asynchronous data output (AUDIO_RX_ENDPOINT) - The host sends USB packets of PCM audio data to this endpoint. The synchronisation method is asynchronous so the Teensy must tell the host how much data to send. Since Full Speed USB packets are sent every millisecond and the sample rate of the Teensy is configured to 44.1KHz this should be either 44 or 45 frames of audio data every millisecond.

#2 An isochronous data input (AUDIO_SYNC_ENDPOINT) - This provides the feedback mechanism from the Teensy to the host by providing a value based on rate at which the audio samples are consumed by the DAC. The host then modifies its transmission rate to match that of the Teensy.

Is my understanding correct?

My first stage of debugging was to compare the Teensy USB audio descriptor with that of a Native Instruments Komplete Audio 6 (KA6) which uses the same method of audio synchronisation (asynchronous data sink with a sync endpoint).

The audio sync endpoint bmAttributes value for the KA6 was defined as 0x11 rather than 0x01 in the Teensy. Looking at this documentation for OSX (https://developer.apple.com/library...ml#//apple_ref/doc/uid/DTS40010116-CH1-TNTAG4) it appears that the data endpoint should be defined explicitly as a feedback endpoint. Value table here: http://www.beyondlogic.org/usbnutshell/usb5.shtml

After making this change the Teensy didn't distort to noise after 130s, however, I could still hear audio glitches which gradually get worse over time.

I have 2 theories now:

1) OSX is responding to the values in the feedback endpoint, but those values are incorrect.
2) The feedback values are out of a certain range and are being ignored by OSX

So now I reach the point where I need help from someone who knows the USB audio code. Here's an abbreviated version of the code which calculates the feedback value found at teensy3/usb_audio.cpp:191. Questions/notes as comments.

Code:
    // From teensy3/AudioStream.h - defines the audio DMA buffer size
    // each sample is 16 bits
    AUDIO_BLOCK_SAMPLES = 128;

    // The number of incoming audio samples which have not yet been 
    // consumed by the DAC
    c = AudioInputUSB::incoming_count;

    // What's going on here? Is diff showing the number of frames left in the buffer?
    // So a larger value for diff increases the feedback frequency, so that the buffer empties slightly quicker?
    // I'm assuming the bit shifting is to do with the 10.14 feedback format
    int diff = AUDIO_BLOCK_SAMPLES/2 - (int)c;
    feedback_accumulator += diff;
    usb_audio_sync_feedback = (feedback_accumulator >> 8) + diff * 3;

Any help with the above questions greatly appreciated.
 
In theory, this is supposed to be a PI (proportional - integral) feedback loop which should converge to "diff" (the error between our actual sample rate and what we're receiving from the Mac) becoming zero, or a small fluctuating number. The addition to feedback_accumulator is the integral part. The bit shift is to scale the accumulator to the range the feedback endpoint uses, and the extra "+ diff * 3" is the proportional feedback component. You could try tuning by changing the 3, and by multiplying or dividing diff by a constant before adding it to the accumulator.
 
Thanks Paul, some more questions:

- Why does the usb callback wait until the incoming_ buffers are full before transferring to the ready_ buffers? Wouldn't it make more sense to transmit data to clients as soon as it's received? Or better: a circular buffer. The delta between the read and write pointers could be used to calculate the feedback for the host.

- Wouldn't a sample rate of 48KHz be better suited for the Teensy? It runs at 96MHz so 96/2000 gives exactly 48KHz. This would mean you could have nicer buffer sizes: e.g. 96 frames for double buffering, rather than constantly going over the buffer boundaries.

- Are there any plans to expose the JTag headers in new Teensys? This would be a massive help in understanding code like this, especially when there's few comments, magic numbers, loops with multiple exit points and gotos.
 
Using a USB protocol analyser I've confirmed the Mac was ignoring the explicit rate feedback endpoint. This is fixed by changing the sync end point bmAttributes from 0x01 to 0x11.
 
I applied the bmAttributes and I'm testing with a mac right now, running the PassThroughUSB example

After a couple minutes of playing, it starts distorting and slowly gets worse until the sound is just clicking noises. The really strange part is I discovered (pretty much by mistake) that if it keeps playing, the problem slowly gets better over many minutes and eventually results in distortion free playing. Or at least I can't hear any problems.

I still don't understand why it's doing this, but my best guess is the Apple driver is very slowly adapting from the Mac's 44.1 kHz rate to Teensy's rate, which is actually 44.117647 kHz (plus or minus the crystal accuracy), and perhaps along the way it loses data? Or maybe Apple is expecting something else from the descriptors? I tried changing the sample rate spec if 44118, but it made no difference. Still experimenting.....
 
Unpleasant news, after quite of lot of testing today, it seems Apple's driver can't transmit 44.1 kHz sample rate to an asynchronous endpoint faster than about 44.112 kHz without distorting the signal. We're running at approx 44.117 kHz. The explicit feedback endpoint is working with bmAttributes = 0x11. For testing, I tried sending fixed values for the feedback endpoint, with some code to print the buffer underrun to Serial1 (which surprisingly don't usually cause a terrible glitch). 44111.25 kHz is right at the point where the Mac sends bad audio. At this point, I'm very sure it's on the mac side. The sound is perfect when the feedback endpoint sends 44.110 kHz or less, but at 44111.25 kHz the mac simply sends distorted audio after several minutes. :(

Looks like we can't use asynchronous mode with explicit feedback. Changing to adaptive mode is goin
g to take a lot of work....
 
Thanks for investigating this and for the workaround for Macs.

From what you've said the problem is that the Teensy isn't creating an exact 44100Hz signal. This is because it's not possible to get an MCLK value which is an integer multiple of 44100.

From your calculations (in output_i2s.cpp):

MCLK needs to be 48e6 / 1088 * 256 = 11.29411765 MHz -> 44.117647 kHz sample rate

But how about if the Teensy aimed for a 48kHz sample rate? This could be calculated as follows, giving exactly 48kHz:

MCLK needs to be 48e6 / 125 * 32 = 12.288 MHz -> 48kHz
 
Last edited:
Status
Not open for further replies.
Back
Top