Teensy 4 SPDIF input + ASRC

alex6679

Well-known member
Hi,

since this is my first post here, I would like to tell something about me. Regarding the teensy I am mainly interested in its audio capabilities. About one year ago I started my first DIY-DSP project. I build a freeDsp which I conrolled with an Arduino via I2C (the first time I came in contact with mirco-controller programming). This setup is also still in use in my hifi-system. However
I am not completely content with it. A few month ago I came across the Teensy 4 with its SPDIF-in and out and the powerful audio library and I decided to give it a try. But I needed the SPDIF input...
After spending some time with the imxrt reference manual, learnig about DMA, how the Teensy audio library works, which additianal hardware is required,... I got it finally working.

What I implemented is:

- A SPDIF input class which receives the audio data and is compatible with the audio library/ inherits from the AudioStream class.
- The SPDIF input class also contains an ASRC algorithm (I implemented this algoritm: https://ccrma.stanford.edu/~jos/resample/resample.pdf)
and also a noise shaping + dithering algorithm. To get noise shaping filter coefficients, I implemented the following algorithm in Python: https://pdfs.semanticscholar.org/ea9c/d52008b1821a1104b5be0d9b18ae63eb410a.pdf
I used the same weighting function as in the paper to get the filter coefficients.

For now I am using a AudioOutputSPDIF3 object to output the audio signal. This object also has the 'update responsisbility'. On the other hand the spdif input receives audio samples at an arbitrary rate (e.g. 48kHz). The signal pipline is as follows:

spdif input (24bit samples) -> ASRC (32bit float computation) -> quantize to 16bit (+ triangular dither + noiseshaping) -> (via blocks/ audio library) spdif output

I am now wondering if there is interest to integrate the spdif input into the audio library? If so, the just let me know how to proceed.

Best regards
Alex
 
If it works and you're happy for anybody to use your code then doesn't really matter if it's in the library or not - just publish it on Github and know that everyone is grateful that SPDIF is at last available. Paul can always then incorporate it at his leisure.
 
Hi, today I planned to make some final test with the spdif input. I especially had a look at the behavior at different input frequency from 44,1 to 192kHz. I ended up with making various changes and I am still not completely content. The main issue is the buffer that is filled by the spdif isr and from which the resampler gets the data. I try to keep that buffer as small as possible to minimize the latency. On the other hand, the resampler should never run out of samples.
I think I still need some days to optimize that part.

Regarding your Teensy: Is it already ready to receive a spdif signal? Are you using a toslink receiver?

Best regards
Alex
 
Hi, today I planned to make some final test with the spdif input. I especially had a look at the behavior at different input frequency from 44,1 to 192kHz. I ended up with making various changes and I am still not completely content. The main issue is the buffer that is filled by the spdif isr and from which the resampler gets the data. I try to keep that buffer as small as possible to minimize the latency. On the other hand, the resampler should never run out of samples.
I think I still need some days to optimize that part.

Regarding your Teensy: Is it already ready to receive a spdif signal? Are you using a toslink receiver?

Best regards
Alex

If you need low latency (as I always do for live sound) but are sourcing data from a variable time dependent source such as a PC, the usual way is once you've written to the buffer, if the buffer is almost empty write the same sample again. As it only tends to occur very rarely it's not noticeable and is better than outputing something random when the buffer is empty.

My Teensy's not set up but I can do. I bought it for a project but there's no way of syncing it to a master clock so I had to go back to a STMicroelectronics based board. But I'll have a play with your code when you publish it anyway as it will make a good interoperability test for that board.
 
Hi,

I finally published the code for the spdif input on github:
https://github.com/alex6679/teensy-4-spdifIn
There are also two very simple examples (example0.cpp and example1.cpp) that show how to use the spdif input class.

It works reliably with my setup, but let's see if it works for you.
There are still some issues that I would like to mention:
- As suggested in the resampling paper, the sinc- resampling filter is also used as anti-aliasing low pass filter. However I restricted the maximum filter length to 161 and depending on the input frequency the attenuation of high frequency is restricted. Maybe an additional anti-aliasing filter should be added.
- The code is not optimized and the processor usage is quite high. At 48kHz input frequency the processor usage of the spdif input is 9%.

The code is not well documented yet and I think there are lots things to explain. Since I don't know if people are interested in my code, I will add some explanations if needed.

Best regards
Alex
 
Since my last post in December, I improve the asynchronous spdif input. Especially the PID controller, that adjusts the sampling distance to the current input frequency. I also added a short documentation and some results of the resampling and noise-shaping algorithms.
Best regards
Alex
 
The spdif input is working since early last year...
Any filters (biquad?) should be optional and handled by the audio library. The resampling sounds interesting.
Maybe you can do a pullrequest for Paul?
 
Additional question: Hopefully, it does not change the Audio-PLL frequency? (->Is it compatible with I2s?)

edit: this
Code:
static int32_t spdif_rx_buffer[SPDIF_RX_BUFFER_LENGTH];
is not good.
Please use DMAMEM (which unfortuately needs added cache handling)
 
I noticed that you already implemented a spdif input some weeks ago, when Paul added it to the audio library. At that point I also already finished my implementation. However, as you mentioned: Since my implementation is resampling the input to Teensy Audio sample frequency, it is still usefull in some use cases.
Regarding the biquad file that you found: No filters are applied to the input signal. I only needed a lowpass filter internally for the adjustment of resampling step width. For that purpose it's unfortunately not possible to use the AudioFilterBiquad class of the audio library.
Regarding the Audio-PLL: It is not changed. Currently I am using i2s inputs/outputs together with spdif in- and output in in one project without any problems.
 
I noticed that you already implemented a spdif input some weeks ago, when Paul added it to the audio library. At that point I also already finished my implementation. However, as you mentioned: Since my implementation is resampling the input to Teensy Audio sample frequency, it is still usefull in some use cases.
Regarding the biquad file that you found: No filters are applied to the input signal. I only needed a lowpass filter internally for the adjustment of resampling step width. For that purpose it's unfortunately not possible to use the AudioFilterBiquad class of the audio library.
Regarding the Audio-PLL: It is not changed. Currently I am using i2s inputs/outputs together with spdif in- and output in in one project without any problems.

Great!
can you fix the DMAMEM )(+cache handling) issue and do a PR?
It can replace my version (my PR was almost a year old when it got merged...) , no problem (but it would be good if it used the same names, so that no code change by the users will be needed)
 
Thanks for pointing out, that I should use DMAMEM. I'll fix that tomorrow. If you really want to replace your version, then I can change the name. But resampling needs quite some ressourcen (Mostly because of the anti-aliasing filter) and some people maybe prefer a version without resampling. I could also rename to something like AsyncAudioInputSPDIF3?
 
Ok, I am using now DMAMEM for the input buffer (+arm_dcache_delete((void*)src, sizeof(spdif_rx_buffer) / 2) in the isr) Also, I added the anti-aliasing attenuation + the minimum filter length of the resampling filter to the arguments of the spdif input constructor. In that way the user can lower the processor usage by decreasing the attenuation and filter length. Do you still think the async input should replace the synchronous input? I could also add a parameter to turn the resampling off in order to keep the current synchronous input?
 
I had a brief look at the current spdif input to check if it makes sense to make a combined class that could be configured as synchronous and asynchronous input. But I think that would make things quite complex. I'll rename my input to AsyncAudioInputSPDIF3 and make a pull request. Let's see if Paul thinks that it makes sense to integrate it into the audio library.
 
What hardware interface are you using for S/PDIF input into Teensy 4? TOSLINK or RCA (coaxial)?
If you are using RCA: is any level shifting or other protection needed, or can the RCA wire and shield be directed connected to Pin 15 and GND, respectively?

I read somewhere that "S/PDIF devices provide signals between 0.2 VPP and 0.6 VPP into a 75 Ω load over an unbalanced circuit. The electrical adapter must amplify the received S/PDIF signal to a logic level accepted by microcontrollers" - but that document was generic, not specific for Teensy.

The Teensy 4 processor datasheet just says it implements the IEC 60958 standard but is not clear if that refers to only the logical and signal timing parts or if that includes the electric characteristics and voltage levels of the specification.
 
Last edited:
Hi, you probably already found out how to connect a rca input, but for the sake of completeness: I use RCA. I implemented a kind of a standard spdif input circuit with a dual line inverter for level shifting and a pulse transformer. So, I didn't connect the cable directly.
 
Hi, thank you @alex6679, that is great info. I have not found any definitive info if an input circuit was needed to Teensy 4, so that was definitely valuable knowledge. I have found some simpler S/PDIF coax to TTL input circuits that have skipped the transformer. Don't know how to find out if such compromises will work or not. Examples of such simpler designs:
http://www.hardwarebook.info/S/PDIF_input
https://sound-au.com/project85.htm (scroll down to Figure 5)
 
Great job! This is exactly what I'm looking for!
I have a quick question that's maybe a little odd. Is the teensy able to operate as a slave device with spdif input? For a project I'm working on, I need an spdif receiver that outputs a constant sampling rate, and the device has to be a slave device since I don't have access to the clock signal. Would it be possible to do this with the ASRC?
 
Great job! This is exactly what I'm looking for!
I have a quick question that's maybe a little odd. Is the teensy able to operate as a slave device with spdif input? For a project I'm working on, I need an spdif receiver that outputs a constant sampling rate, and the device has to be a slave device since I don't have access to the clock signal. Would it be possible to do this with the ASRC?
Never Mind,I looked at the documentation a bit. This isn't possible. My apologies for not looking into it more first.
 
I think I don't completely understand what your task is. You want to use the Teensy as a slave? Does that mean that you want to use the spdif input signal to clock the audio pipeline? One feature of asynchron resampling is that audio data can be transfered over different clock domains. Therefore it is normally not necessary to access the clock of the source. There is an example that uses the asynchron spdif input: https://github.com/alex6679/teensy-4-spdifIn/blob/master/examples/example_spdif.cpp Here all input signals are resampled to 44.1 kHz and are sent out via the spdif output. The internal clock of the Teensy is used. But that is not what you need?
 
Kind of... I am using an ADAU1401. In this case, the ADAU1401 has to be the clock master. Most of the time, when you are using spdif, the output sampling rate is supplied by the spdif signal, which you know. In this case, because the specific board I have has an onboard oscillator, and I don't have access to the MCLK input, any device connected to it has to be in the same clock domain, and synchronous to the ADAU1401.Also, due to the fact I don't have access to the MCLK input, the sampling rate has to be locked on startup. I double checked this over on Analog Devices EngineerZone. So, what I was asking was if it is possible to have the Teensy take all of it's clock signals (LRCLK, BCLK, MCLK) from an external device and output synchronous to these with spdif input. As I mentioned, Looking through the forums and the documentation I don't think this is possible. Instead, I have just decided to use the Teensy for everything, since it's more than capable. Again,my apologies. I should have done a better job of looking through the documentation before posting. I'm pretty new to the forums.

I do want to say thank you though for replying and also all the hard work you've done on the spdif input. I wouldn't be able to use the Teensy for this project without it.
 
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.
 
Back
Top