Teensy audio cracks (nearly) solved.

454 views
Skip to first unread message

"Christoph v. Wüllen"

unread,
Feb 25, 2021, 10:26:51 AM2/25/21
to Steve Haynal, herme...@googlegroups.com
Dear Steve (and group)

for me, the Teensy4 audio crack problem is almost solved.

The experimental observation so far was:

- when audio cracks occur, this is always when
in in TeensyAudioTone::update() a receiveReadOnly()
returns NULL

- it may take 5-10 min for the first crack to happen,
but then they occur regularly all 18 seconds
(or so, cum grano salis)

Therefore I suspected something has been messed up in the
sync messages.

Indeed, when commenting out two updates to the variable
feedback_accumulator in file

./Java/hardware/teensy/avr/cores/teensy4/usb_audio.cpp

then audio cracks are not completely gone, but the first
one occurs after 7 minutes and the second one after 14
minutes (!), look at this debug output

9.378: UR= 173
39.879: UR= 173
40.380: UR= 172
40.881: UR= 173
41.382: UR= 172
41.883: UR= 173
42.384: UR= 173
42.885: UR= 49
432.663: UR= 1
909.114: UR= 1
1391.577: UR= 1
1882.557: UR= 1


every 500 msec, the summed underruns in TeensyAudioTone::update()
are printed on the serial monitor. At first you get 172 underruns
in each half a second because the audio output was not yet opened
(44.1k is 344.5 blocks per second), but then it stops.

432 secs after powering up the teensy there is an underrun (and an audible crack),
but the next ones are after 909, 1392 and 1882 seconds, which means that there
is one crack every 8 minutes which you do not notice when operating HF.

To summarize, the update of „feedback_accumulator“ looked VERY suspicious
to me (see my last email), refraining from any updates (leave it fixed at
its initial value) is far better than what is implemented.

The suspicious statement is of course

if (!left || !right) {
usb_audio_underrun_count++;
//printf("#"); // buffer underrun - PC sending too slow
//if (f) feedback_accumulator += 3500; <=========== commented out!
}

but I also commented out the „other one“ (although this is probably not
needed and improves nothing)

if (f) {
int diff = AUDIO_BLOCK_SAMPLES/2 - (int)c;
//feedback_accumulator += diff * 1; <=========== commented out!
//uint32_t feedback = (feedback_accumulator >> 8) + diff * 100;
//usb_audio_sync_feedback = feedback;

//printf(diff >= 0 ? "." : "^");
}


both are in void AudioInputUSB::update(void). These were the ONLY changes to the
USB audio subsystem and it had such a huge impact.


I now leave it as it is, HiFi quality is not needed.


Yours,

Christoph.

Roger Critchlow

unread,
Feb 25, 2021, 11:33:45 AM2/25/21
to Christoph v. Wüllen, Steve Haynal, Hermes-Lite
Very nice Christoph --

As I recall from SDR-Widget, Alex Lee spent a long time getting rate feedback right.  I don't think I ever figured out what the feedback number actually expresses.

It sounds like underruns are too coarse a signal.  Isn't the driver keeping a running average of actual sample rate?   Or audio packet arrival intervals?  Maybe adding a few more bits of precision to that will pick up this case.  The driver has minutes to detect the accumulating error before the underrun happens.  It should be possible to nudge the feedback to reverse the drift, giving minutes to detect the accumulating error in the other direction and find another rate feedback nudge.

Or maybe the rate feedback doesn't actually work?   There were a lot of sdr-widget firmware releases with that feature.

-- 73 -- rec -- ad5dz --

--
You received this message because you are subscribed to the Google Groups "Hermes-Lite" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hermes-lite...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/hermes-lite/A4692E0A-4B13-49A0-9B2A-CBF97A251258%40darc.de.

Steve Haynal

unread,
Feb 25, 2021, 3:37:51 PM2/25/21
to Hermes-Lite
Hi Christoph and Group,

I believe I have already solved this. At least in my setups I do not hear cracks anymore for over a week now. I am running at 48khz and with smaller buffers. Sorry that I have not posted details or code yet, but I am seeing some differences in how Windows handles lower values of bInterval and am still making some changes to accommodate that before committing code.

The line below is necessary. It is what provides frequency feedback to the PC so that the PC can adjust its rate. It is the heart of USB audio in asynchronous mode. You shouldn't comment it out.
//feedback_accumulator += diff * 1; <=========== commented out!

The feedback_accumulator value is used to generate the 16.10 fixed point standard USB feedback value for the PC. This feedback value is what the USB gadget thinks its frequency is relative to the PC host. For example, take the initial value assigned to this register, 739875226. The actual feedback value that the PC sees is 739875225/(2**24) = 44.1000. The commented out code line above slowly adjusts this frequency over time as intended by the USB standard to synchronize the PC with the USB gadget. The Teensy code cleverly uses this mechanism to both synchronize clocks and keep 1.5XAUDIO_BLOCK_SIZE of data buffered. You are lucky that your Teensy 4.0 clock and PC clock are quite close in frequency so that you only hear a click every ~8 minutes. This will not be the case for everyone. This feedback is required to properly synchronize clocks and avoid any clicks.

The other line you comment out is not technically necessary, but I believe the intent is to "bump" the frequency a bit so that the device does not enter a scenario with too little data buffered so that jitter causes frequent underruns.
//if (f) feedback_accumulator += 3500; <=========== commented out!

There is a bug with the above line at least for the Teensy 4.0 platforms using USB2.0 that I have played with. For some reason that I don't understand yet, the PC will send the USB gadget audio packets of length 24 bytes every ms for the first second of operation. This causes multiple calls of the above line and results in the feedback_accumulator value being severely overcorrected. After a short initial time, the PC then starts sending packets of length 172 to 180 bytes every 1ms as expected for 44.1kHz. But the feedback_accumulator is so far out of alignment that it will take up to ten minutes for frequencies to resynchronize. To avoid this bug, change the line

 AudioInputUSB::receive_flag = 1;

in usb_audio.cpp to

if (len > 100) AudioInputUSB::receive_flag = 1;

I think you can uncomment the code you commented out and make this one change and all your clicks should go away. I'm interested in learning if that really is the case for you.

73,

Steve
kf7o

Roger Critchlow

unread,
Feb 25, 2021, 4:31:02 PM2/25/21
to Steve Haynal, Hermes-Lite
Steve --

That will be very neat, and saves me from begging off fussing with the USB code.  

In the last hour I just figured out how to get Ctrlr to produce the MIDI NRPN messages I wanted for setting values in the keyer.  It only took a day to figure out which of the ~100 slots needed to be set for each component.  Very gnarly to program, but it works fine in the end.  Now to rough out the rest of the panel and the controls.

Is there some trick to saving Ctrlr programs?  Whether I Save, Save As, or Export, it just locks up with the File menu title lit up.  Maybe a missing library, but none show up in ldd.

I have most of what I plan to do sketched out, a few parts aren't finished, a few are broken, and several haven't been tested, but I think we can do pretty much everything that's come up, and we can do it on both Teensy 3 and Teensy 4, and we can do some of it even without the audio card.

-- 73 -- rec -- ad5dz --


1) A USB audio card with 2x2 line-in line-out capability.  The line-in sensitivity and line-out level are adjustable.
2) A USB audio card with stereo headphones, integrated headset microphone and headset control buttons.

Steve Haynal

unread,
Feb 26, 2021, 2:01:13 AM2/26/21
to Hermes-Lite
Hi Christoph and Roger,

I may have spoke too soon. This evening filtering by len > 100 did not help. There is still something happening during the first second which causes the feedback_accumulator to go too far out of spec by the += 3500 statement. Either packets are sent too slow, not 1ms apart, or packets are too short. 

I did fork the Audio and core repositories and am adding my changes for 48kHz. I had everything working well with Linux where data was set at USB2.0 0.125 us intervals. This was great as smaller packets meant the feedback could be more accurate. This is also how I see 3 professional USB audio cards are setup. But when I tried this on Windows it did not work. The Teensy is a UAC1 audio device. UAC1 is supported by USB 2.0, but it turns out Windows doesn't like a bInterval of 0.125us with a UAC1 device. It might be possible to convert to UAC2, but Windows only released a generic UAC2 driver in 2017. I am wary there will be compatibility problems if it is a true UAC2 device.

73,

Steve
kf7o
Reply all
Reply to author
Forward
0 new messages