Teensy 4 SPDIF input + ASRC

Ah, ok. I think I understand. I also think that only using the Teensy is probably the simplest solution. The funny thing is, I think that I use a setup that is very similar to the one you first planned: I use 4 Teensys. All are connected via I2S (I only use LRCLK, BCLK and the data line and don't connect MCLK) and one of the Teensys is the master. The other 3 are i2s slaves and use the incoming bitclock to also clock their spdif output. Hence the complete audio piplines of the slave Teensys are clocked by the master Teensy. Of course, I needed to make some minor changes to the audio library. Anyway, you are right, if a single Teensy can do all the work, it's easier to not add the additional dsp.

Yeah, that's pretty cool. I started with the ADAU1401, I realized after a while that I needed SPDIF input. So I ordered a larger DSP that has an integrated transciever, a bunch of ASRCs, and a massive number of i2s lines. The board shipped, but with everything going on, it's stuck and I have no idea when it's going to come. So, I've been exploring other options. When I started, I actually looked at the Teensy, but at that time, the 4.0 had just come out, and the audio library wasn't ready yet. So anyways, that's my story. Thanks for the help!
 
Hi alex6679, regarding the AsyncAudioInputSPDIF3 example that crashes the Teensy 4.1, you asked to post here, from another thread: I really stripped down my code, and the following seems to crash (or at least severely confuse) the Teensy, but if you swap out the commented lines, it works perfectly well as a SPDIF through. I'm new to Teensy, though not to Arduinos in general, so forgive me if I'm doing something daft!

Code:
#include <SPI.h>
#include <Audio.h>
#include <Wire.h>
#include <SD.h>
#include <SerialFlash.h>

AsyncAudioInputSPDIF3    spdif_async1;   //xy=200,294
AudioOutputSPDIF3        spdif3_1;       //xy=633,316
AudioConnection          patchCord1(spdif_async1, 0, spdif3_1, 0);
AudioConnection          patchCord2(spdif_async1, 1, spdif3_1, 1);

//AudioInputSPDIF3         spdif3_2;       //xy=193,232
//AudioOutputSPDIF3        spdif3_1;       //xy=633,316
//AudioConnection          patchCord1(spdif3_2, 0, spdif3_1, 0);
//AudioConnection          patchCord2(spdif3_2, 1, spdif3_1, 1);

void setup() 
{
  AudioMemory(12);
}

void loop() 
{

}
 
Tom and I are having similar issues, but a basic example of my problem looks like:

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AsyncAudioInputSPDIF3    spdif_async1;   //xy=177,236
AudioMixer4              mixer1;         //xy=404,401
AudioRecordQueue         queue1;         //xy=616,366
AudioConnection          patchCord1(spdif_async1, 0, mixer1, 0);
AudioConnection          patchCord2(spdif_async1, 1, mixer1, 1);
AudioConnection          patchCord5(mixer1, queue1);
// GUItool: end automatically generated code

void setup(){
  AudioMemory(12);
}

void loop(){}
 
Surprisingly, this is very similar to the Examples>Audio>HardwareTesting>PassThroughAsyncSpdif example, which works for me
 
Surprisingly, this is very similar to the Examples>Audio>HardwareTesting>PassThroughAsyncSpdif example, which works for me

Hmm, I've not tried that one. But, looking at it, the obvious difference is it passes some parameters in the AsyncAudioInputSPDIF3 declaration. Is the Audio System Design Tool just neglecting to add those to its code?

Code:
AsyncAudioInputSPDIF3     spdifIn(true, true, 100, 20);
 
The main problem that caused the crash is a bug in the async spdif input. The spdif receiver is configured after the dma transfer is already started, but the receiver should be configured first. I can't remember at which point I switched that accidentally. I didn't noticed it because if the spdif output constructor is called first (as in the example), the spdif interface is configured correctly before the dma of the input starts. Anyway I fixed that. But I need to do some tests before I commit the fix to my Github repository.
There might be another problem at the example of barley. I think the AudioRecordQueue will cause a problem, since only 12 audio blocks are allocated. I assume AudioRecordQueue object will buffer all of them and the spdif input will run out of blocks. But I am not sure about that, since I never used that object.
 
if the spdif output constructor is called first (as in the example), the spdif interface is configured correctly before the dma of the input starts.

Yep, just swapping the order seems to have sorted it for me - thanks! I was just using the order that the audio design tool code used.
 
As far as I know, it's better to first initialize the input object, so that they are updated first during the audio processing. If the outputs are initialized first, some additional latency is introduced. But I am not 100% sure about that. But for now you could of course use that workaround.
 
There might be another problem at the example of barley. I think the AudioRecordQueue will cause a problem, since only 12 audio blocks are allocated. I assume AudioRecordQueue object will buffer all of them and the spdif input will run out of blocks. But I am not sure about that, since I never used that object.


Switching the order also prevents the crash for me, but you're right, something is odd about the output: I use a AudioRecordQueue and AudioOutputSPDIF3 together, and there's about a 500ms(-ish) difference between the queue output and the spdif output, which causes significant latency in the wireless link.

The same output combination used with the normal AudioInputSPDIF3 input has a latency of about 10ms. I get that the processing would add a little time, but that would affect both outputs the same, I would think.
 
Ok, I made now some changes at the asynchronous input. It is now also a friend class of the AudioOutputSPDIF3 and uses (as the other spdif input) also config_spdif3() of the output class to configure the spdif input. I tested it again at different samping rates and, of course, also changed the order of in- and output. I commited it to my fork of the Audio library: https://github.com/alex6679/Audio.

The files that changed are:
async_input_spdif3.h
async_input_spdif3.cpp
Resampler.h
Resampler.cpp
output_spdif3.h

PassThroughAsyncSpdif.ino

Regarding your problem with the very high delay of about 500ms. I don't know if you are Ok with manually changing files of your audio library. If yes, then you could update the 6 files above. The new serial output in 'PassThroughAsyncSpdif.ino' will maybe provide a hint. The 'buffered time', the input frequency and the the group delay of the resampling filter would be interesting.
At the moment I have no clue what could cause that large delay. Typically the the latency of the input is about 1ms and the group delay is in the same range.
Could you also provide a sketch at which you have the problem?
 
I updated my files and have it working now! The latency issue exists if either or both the dither or noise shaping attributes are set to true. It actually starts out in sync and slowly gets further and further out of sync (up to about 500ms) with either/both attribute(s) enabled. If they are both false, it plays in real time with no issues.
 
I am glad that it is working, but I am still wondering about that high latency in combination with dithering/noise-shaping. Is your setup quite performance demanding? Did you have a look at the processor usage? Are you sending lots of (debug?) information to the PC via serial connection? I encounter once problem, when I plotted too much debug output in the Arduino IDE serial plotter. However, I encountered noise and distortion and not a delay.
 
Alright, I had it output the info every second:

EDIT: This is with both attributes set to false and 15 audio blocks designated. Setting both to true ups the processor usage to 8%, but the other numbers look similar.

Screen Shot 2020-06-04 at 2.36.20 PM.png

There is not really any serial output, the only processing is just SPI for sending the data from the T4.0 to the NRF24L01+. I discovered that the more audio memory blocks I add, the longer the latency becomes, so that explains the multi-millisecond delay, I had 200 blocks set for audio.

Here is a somewhat abbreviated version of the code:
Code:
#include <Audio.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <EEPROM.h>

#define audioBufferSize 128

#define addrEEPROM 0
#define LED_PIN 3
#define CE_PIN 5
#define CSN_PIN 9

RF24 radio(CE_PIN, CSN_PIN);

const byte pipeaddress[][6] = {"1Ad", "2Ad", "3Ad"};

// GUItool: begin automatically generated code
AsyncAudioInputSPDIF3    spdif1(false, false, 50, 20);
AudioEffectDelay         delay2;         //xy=444,196
AudioEffectDelay         delay1;         //xy=446,65
AudioMixer4              mixer1;         //xy=447,333
AudioRecordQueue         queue1;         //xy=659,328
AudioOutputSPDIF3        spdif2;
AudioConnection          patchCord1(spdif1, 0, mixer1, 0);
AudioConnection          patchCord2(spdif1, 0, delay1, 0);
AudioConnection          patchCord3(spdif1, 1, mixer1, 1);
AudioConnection          patchCord4(spdif1, 1, delay2, 0);
AudioConnection          patchCord5(delay2, 0, spdif2, 1);
AudioConnection          patchCord6(delay1, 0, spdif2, 0);
AudioConnection          patchCord7(mixer1, queue1);
// GUItool: end automatically generated code

uint8_t bufr[audioBufferSize * 2];

// The last saved delay value is stored in EEPROM memory address 0.
int del = EEPROM.read(addrEEPROM);

void setup(){
  Serial.begin(115200);
  delay(100);
  
  AudioMemory(15);
  
  mixer1.gain(0,0.5);
  mixer1.gain(1,0.5);
  
  delay1.delay(0, del);
  delay2.delay(0, del);
  
  radio.begin();
  radio.setPayloadSize(32);
  radio.setAutoAck(false);
  radio.disableCRC();
  radio.setPALevel(RF24_PA_MAX);
  radio.setDataRate(RF24_2MBPS);
  radio.setRetries(0,1);
  radio.setChannel(122);
  delay(50);
  
  radio.openWritingPipe(pipeaddress[0]);
  radio.stopListening();
  delay(100);
    
  queue1.begin();
}


void loop(){
  if (queue1.available()){
    memcpy(bufr, queue1.readBuffer(), audioBufferSize*2);
    queue1.freeBuffer();
    for (int i=0; i<8; i++){
      radio.writeFast(&bufr[i * 32],32);
    }
  }
}
 
Thank you for the code and the information. The buffered time, input frequency,... are totally normal. The processor usage of the audio library is quite low, but we don't know how many recources are needed by the other tasks. I had another look at the dither and noiseshaping algorithms and tried to reproduce your problem, but I had no luck.
You mentioned that the delay also occurs if only the dither function is activated. Here is the only difference at the algorithm between active and non-active dithering:
Code:
if(_dither){
    const uint32_t r0=random(1000000);
    const uint32_t r1=random(1000000);
    xnD=xn + (r0 + r1)*f2-1.f;
}
else {
    xnD=xn;
}
So I don't think that the delay is caused by the dithering alone. What do you think? Is there the chance that the delay is caused by the additional processor usage caused by the dithering + e.g. the SPI communication? If you are still motivated to find out what's going on, you could for example deactivate the dithering + add some dummy audio filter that increases the processor usage again. Maybe the delay appears also in this scenario.
 
I appreciate all of the guidance and effort you're putting into this!

I may not have been perfectly clear about the delay, so let me re-describe my setup and the issue, for clarity's sake:

I'm using two teensy 4.0s, each with an NRF24L01+ RF transceiver.
- Teensy #1 is configured as the transmitter, with S/PDIF-ASYNC in, an S/PDIF out passthrough, and an AudioRecordQueue used to send the data over the RF module.
- Teensy #2 is configured as the receiver, with an AudioPlayQueue in and S/PDIF and I2S out.

Teensy #1 works flawlessly in and out. There is never lag in the S/PDIF out here. Teensy #2 plays perfectly with dithering/noise shaping set to false, but is the one that comes out of sync when using these, so the issue must lie somewhere in the queues. I'm not sure how you tried to reproduce the problem, but I just wanted to make it extra clear where the issue occurs. I'm honestly not worried about needing to set those attributes, as it sounds fine without them, and it is currently working as desired.

But if it would help improve this module, I can definitely keep testing things. The dithering doesn't look like it should affect the audio output unless random() is extremely slow or something, but I don't know much about how the teensy generates pseudorandom numbers.
 
Ok, now I understand your setup. I didn't try to reproduce exactly your setup, but had closer look at delays and compared some waveforms with and without dithering. My reasoning was that maybe I also had the delay but just never noticed it. Especially since it only builds up slowly.
The processor load of the pseudorandom generator is also captured by the processor usage output and is part of that 3% increase that you noticed. Hence this task does not take too much time.
You are right the dithering/noise-shaping barely makes a difference and you can just turn it off.
I just would be interessed in the last test that I mentioned above: Does the delay also occur if dithering/noise-shaping is turned off, but processor usage of the audio pipline is increased in another way, for example by increasing the 'minHalfFilterLength' of the spdif input to 80.
 
Can confirm that setting the minHalfFilterLength to 80 caused a similar delay. Setting attenuation >100 also causes it.
 
Thank you for testing that. I am really sure now that the delay is not caused by a bug of the spdif input. I think the following happens:
Your program slightly exceeds the capabilities of the teensy if dither is active. The audio blocks accumulate in the queue object until all available audio blocks are allocated. The spdif input only resamples new audio data if two audio blocks are available. Therefore the processor load decreases when all audio blocks are occupied. An equilibrium is set up at which some audio data is lost from time to time. I think that would explain everything: the longer delay when more audio blocks are available and also why the delay doesn't occur at the spdif output of teensy #1. I saw that the AudioRecordQueue class has a function called 'available()', that returns the number of blocks that are currently buffered. You could use that to check if some delay is building up, but from my side no further tests are required.
If you encounter some other problems there would be another way to debug the async spdif input, that I nearly forgot: In the async_input_spdif3.h header there is the define 'DEBUG_SPDIF_IN', which is currently commented out. You can use it to receive more debug information. Most of the information is difficult to interpret, but it will also inform you if no audio blocks are available.
There is one thing I need to add the input. The user should be able to restrict the maximum length of the resampling filter. Currently you probably would run into problems if the spdif input frequency is higher than 48kHz, since the filter length/ processor usage would increase automatically.
 
Surprisingly, this is very similar to the Examples>Audio>HardwareTesting>PassThroughAsyncSpdif example, which works for me

I would like to try this example too. Can you please explain the hardware and schematic I need to interface my optical cable to my Teensy 4.0 and audio adapter. Thanks.
 
Hi,
I don't know/own an audio board, but I can tell you how I interface the Teensy with an optical cable.
I use this optical receiver:
https://www.cliffuk.co.uk/products/optical/FCR684205R.pdf
On the last page there is a small schemantic and I added the two by-pass capacitors as described.
Besides that you need to connect a 3.3V power source and GND. I have a separate power supply for all my peripheral hardware, but you can also use the 3.3V output of the Teensy. The receiver doesn't draw much current. Vout of the receiver needs to be connected to pin 15/SPDIF-IN of the Teensy.
 
Hello. Thanx for the info. Yes, I will try that. If it works OK with the Teensy Async spdif input, I will replace my audio connector input with it. It's for my AGC/Compressor box (see below). How well does that ASRC software work? If you have a clean input sine wave with sample rate of 44100Hz +- 100 ppm precision, how is the total harmonic distortion after conversion? I did 2 ASRC's. One for a V.32 bis modem where SNR > 40 dB was OK. Another one, I simulated in C (floating point) using a polyphase Sinc filter (89x15phases) and a 3rd order interpolator. I was getting a THD around 130dB on 24-bit I/O audio wav files but there was a lot of calculations needed (4 convolutions per output sample)!


https://forum.pjrc.com/threads/68320-New-Guy-Looking-For-Teensy-DSP-programming-tips/page3
 
That's difficult to answer. Some months back, I did some tests in which I want to get a feeling about the amount of jitter of the Teensy spdif-output.
I can share the results although they don't exactly answer your question.
First some background on what I wanted to find out:
I use 4 Teensys 4.0 as multichannel processor in my surround system. My speakers are digital active speakers and they are connected via coax spdif cable to the Teensys.
I was therefore curious about jitter of the Teensy spdif output signal. Maybe my dIY solution introduces lots of jitter and totally degrades the sound quality.
I don't have the equipment to measure the jitter of the spdif signal directly and if I could it would be difficult for me to asses its influence on the signal.
Also, the ASRC of the speakers removes the jitter from the Teensy to some degree and I was more interesseted in the remaining jitter that gets baked into the signal at the
resampling. I also don't have access to the resampled I2S signal inside my speakers, but I had an unused board with an Cirrus Logic CS8422 ASRC lying around and another Teensy 4.1.
So I decided to build the following setup:

- One of the Teensy 4.0 outputs a J-test signal via coax spdif output.
- This spdif output is connected to Cirrus Logic CS8422 board.
- The CS8422 resamples the signal and is connected to the Teensy 4.1 via I2S. (The Teensy 4.1 is I2S master)
- The Teensy 4.1 sends the resampled signal to the laptop via usb serial, were I can have a look at its spectrum.

I assume that both Teensy have the same amount of jitter and the result shows the effect of the jitter of both Teensys combined. My reasoning was that the dsp and ASRC of
my active speakers probably perform equal or better that the quick and dirty setup and if the distortion of the received J-test signal is reasonable low, I don't have to worry about jitter.
I first used the CS8422 hardware resampler instead of the software resampler because I thought that it would provide more comparable results to the resampler of my speakers.
But out of curiosity, I also connected the spdif output of the T4 to the spdif input to the T4.1 and used the software resampler. That's why I think the test is also interesting for
you.

Here is the result when the Teensy software resampler is used:
softwareResampler.jpg
softwareResampler_zoom.jpg

In my setup the Teensy run at 48kHz instead of 44.1kHz.
The blue spectrum is the resampled j-test signal from the T4.1. The FFT was applied to 96000 samples and a Hann window was used.
The orange spectrum is the resampled signal without any jitter. I used a python implementation of the resampling algorithm that runs on the Teensy. According to
to the receiving T 4.1 the average input frequency at the test was 48000.04 kHz. So I resampled the j-test signal from 48000.04 kHz to 48000 kHz in python as a reference without
jitter.

The following to images show the result of the hardware resampler. The orange spectrum is again the result of the python resampling.
CS8422.jpg
CS8422_zoom.jpg
 
VERY IMPRESSIVE! I ordered the connector you suggested and I will try it out, recording(via an SDCard on my audio adapter) a sine wave from a wav file stored on my TV audio player. I don't know how much degradation I will encounter between the sine wav and the TV optical output but I will compare differences between audio & optical signals.

Thanks a lot for your answer!
 
@Bill Glass: I was also pleasantly surprised about the low distortion and I am curious about your results. Two things that you need to take into account: When the resampler is initialized, it uses the input frequency provided by one of the spidf registers (SPDIF_SRFM). This frequency is not very accurate and within the first few seconds the ratio from input to output frequency is adjusted. So, you would need to discard the first few seconds at your evaluation. The second point is: I use a 32bit floating point version of the audio library. If you use the standard 16bit version, your noise level will be higher.

@MatrixRat: I use a standard output circuit and also had a look at the signal with a scope. The waveform looked nice and there was not much overshooting. However, it was only 100MHz scope and the highest frequencies were attenuated. There are lots of discussions in hifi forums about jitter at spdif signals, if input and/ or transformers should be used or not,... After reading all that discussions and how bad the spdif interface is, I was uncertain about my implementation. But the waveform looked nice, the results with this j-test signal were ok and I am content with the overall sound quality of my system. So, I don't feel unsettled anymore when I read about all the problems of the spdif interface.
 
Back
Top