Buzzing Sound with Raspberry Pi 4 and Teensy 4.0 via I2S

bthayer2

Member
Hi,

First let me preface this with a quick apology for the lengthy post as I wanted to be thorough. I am a bit over my head with a lot of this audio stuff and have been stuck on this problem for about 3 weeks so really appreciate any guidance anyone has to offer.

I am hoping to resolve a buzzing issue I am experiencing when sending audio from Raspberry Pi 4 to Teensy 4.0. I am sending audio over via I2S which is 16 bit at 48 kHz. I am aware the Audio library supports 16 bit at 44.1 kHz, so I suspect this frequency misalignment is causing the buzzing issues. The Raspberry Pi 4 is supposed to be the master generating the clocks and sending over the audio data via I2S and then the Teensy is supposed to be a slave which receives the audio via I2S and outputs the audio via USB.

Below is how I am sending over a sample wav file from the pi to the teensy.
Code:
aplay -D plughw:CARD=sndrpirpidac,DEV=0 /usr/share/sounds/alsa/Front_Center.wav

"sndrpirpidac" is the i2s enabled via "dtoverlay=i2s-dac" in the boot config of the Raspberry Pi 4 for anyone curious.

The below command sends over a sine wav on 2 channels over i2s using 48 kHz where "hw:0,0" is the is2. This sends sine wav on left channel for about 3 seconds and then right channel, constantly alternating until stopped.
Code:
speaker-test -D hw:0,0 -c 2 -t sine -f 1000 -r 48000

When I send a sine wave at 44.1 like this:
Code:
speaker-test -D hw:0,0 -c 2 -t sine -f 1000 -r 44100

Then the sound does not have the buzzing. I have attached this audio sample in the zip file below by the way. Since this test did not have buzzing, this leads me to believe the frequency misalignment is the source of my problems so I have attempted to make some changes to the Audio library to get 48 kHz to work which has led to a bunch of rabbit holes.


Testing Audio Library Changes Steps
When I modify the Audio library source (cloned repo from here), these are the steps I was always doing during my tests:
Make test changes to Audio library source files. Close Arduino IDE, reopen Arduino IDE, compile the same Arduino code again, unplug Teensy, then uninstalling Teensy from Device Manager, and then plug Teensy back into computer USB.


What I Have Tried:

Changed my I2S wires from 20 cm to 8 cm wires (which are what are shown in the wiring picture below). I have read that long wires can cause I2S issues so early on I thought this was the issue but this was quickly disproven as the 44.1 sine test explained earlier works fine.

Setting "AUDIO_SAMPLE_RATE_EXACT" in "AudioStream.h" to 48000. Then sending over 48 kHz sine wav.

Setting "AUDIO_SAMPLE_RATE_EXACT" in "AudioStream.h" to 48000. Updating "usb_desc.c" by changing all "LSB(44100), MSB(44100), 0," lines to "LSB(48000), MSB(48000), 0," Then sending over 48 kHz sine wav and Front_Center.wav file. Audio samples for both of what was received from these 2 tests are in the zip file below. Note: These 2 changes alone make the device appear as 48 kHz in windows (screenshot later).

Considered sending audio out of Raspberry Pi via headphone jack to audio shield here which would then connect to Teensy over I2S. However, I run into the same problem where this shield accepts 44.1 kHz audio and not 48 kHz as it uses the same audio library when incorporating with the Teensy.

Explored using Teensy4i2s library since it supports 48 kHz over I2S but found out quickly this library does not have usb output implemented.

Using OpenAudio_ArduinoLibrary which has support for 48 kHz. However, this currently does not have I2S slave input implemented. I attempted to add the functionality (pr is here if curious) which unfortunately still resulted in the same buzzing/audio distortion issues.

Researched connecting Raspberry Pi 4 to Teensy 4.0 as slave using 48 kHz. I cannot find any examples of this done.

Researched lots of PJRC threads for converting the Audio library to 48 kHz. I have attempted a bunch of other small minor changes to the source library as well (files input_i2s.cpp, AudioStream.cpp, usb_audio.cpp). I lost track of all of these changes as the more I did the worse the audio got. It got to a point where it was just static white noise coming through so I reverted everything.


Extra Context:

You may ask why not send audio from Raspberry Pi 4 at 44.1 kHz. Yes I have considered this and did attempt this at first but ran into more problems. The board defaults to 48 kHz and trying to override this is not easy especially since 48 kHz seems hardcoded for dmix. Plus, I would prefer to keep it all working in 48 kHz as I have other stuff on the Pi I am trying not to break.

In Arduino IDE, I am selecting Teensy 4.0 board and USB Type Audio. See screenshot below:
Arduino Compile Settings.png


Teensy Code used in Arduino IDE:
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// --------- AUDIO OBJECTS ----------
AudioInputI2Sslave     i2s_in;
AudioOutputUSB        usb_out;
AudioConnection       patchCord1(i2s_in, 0, usb_out, 0);
AudioConnection       patchCord2(i2s_in, 1, usb_out, 1);

void setup() {
  AudioMemory(20);
}

void loop() {
}

Raspberry Pi 4 to Teensy 4.0 I2S wiring:
Teensy Wiring.jpg



Note: I don't currently have an oscilloscope so hard for me to confirm the clock data is being sent over correctly, but open to getting one to research/investigate this further if that's what it takes.


I would have thought that only setting "AUDIO_SAMPLE_RATE_EXACT" in "AudioStream.h" to 48000 was the main change that was needed from the software side and updating usb descriptors in "usb_desc.c". Doing these 2 tweaks makes Windows think the Teensy device is 48 kHz as shown below:
Teensy Appears As 48 kHz.png


However, I still have the buzzing problem so I think there are other places in the code that need updating as well. My best guess is that "AUDIO_SAMPLE_RATE_EXACT" is needing implemented in more places like constants or some other math formulas. For example when looking through the Audio library I found the below in "usb_audio.cpp":
feedback_accumulator = 739875226; // 44.1 * 2^24

Which makes me think it should be updated to something like this:
feedback_accumulator = AUDIO_SAMPLE_RATE_EXACT * 2^24;


With only needing I2S slave input and usb audio output, I don't think there are a lot files that would need modified. I was looking specifically at these
input_i2s.cpp, AudioStream.cpp, usb_audio.cpp, output_i2s.cpp, usb_desc.h, usb_desc.c

Does anyone else have any ideas on what to try modifying or think I am missing other files to look at? Let me know if you have any ideas or modifications to files that I should try. Thanks in advance!

Zip file link, sorry I tried to upload the zip file here in the thread but got a file too large for processing which is weird because it is less than 3 mb. Check out the "Front_Center" wav file examples too to hear normal audio, you will hear the buzzing at the end of each word.
 
Windows can act very strangely when a previously used USB device changes its audio config. I would recommend getting USBDeview and fully removing any existing driver instances of the Teensy from the PC.
 
I was using Device Manager to uninstall the device driver which seemed to do the trick. When I would make changes to the Audio library and then did my "Testing Audio Library Changes Steps" above, then I saw changes in the recorded sine wav audio. Just to be sure, I did the same steps again but one time using Device Manager and another time using USBDeview to remove the Teensy driver. As shown in my screenshot below, the audio files for both methods still show the buzzing/unexpected distortions so I don't think it is a Windows driver caching issue.
 

Attachments

  • Sine Wav Differences From Uninstall.png
    Sine Wav Differences From Uninstall.png
    54.3 KB · Views: 15
Have you tried the updated Teensy USB audio code? There’s an extensive thread, with the useful bits starting about here. Previous posts looked promising, but the effort fizzled out unfinished.

The stock USB code just about works for 44.1kHz stereo with 128-sample audio blocks, but as you’ve discovered doesn’t really lend itself to being tweaked in any way. The new code seems pretty robust, and the main author @alex6679 is as responsive as Real Life permits when a bug is found! I’ve contributed a few lines of code, some testing, and general cries of encouragement… Judging by the thread, there’s at least a few users out there, and they seem happy.

At the moment “installing” it is a bit convoluted, but it’s possible it’ll make it into Teensyduino at some stage. 1.60 should release soon, then it’s a matter of what Paul wants to include in 1.61.
 
It is funny you mention that because I was just looking at that thread and even bookmarked the GitHub earlier. I wondered if my issue stemmed from the usb processing side of the code, like the “core” project logic. I have been studying the I2S input more so lately and was starting to rule it out. I will have to research the usb logic more and how to use the repo. I may attempt to pull some of those files in and test things out. I will report back.
 
feedback_accumulator = 739875226; // 44.1 * 2^24

Which makes me think it should be updated to something like this:
feedback_accumulator = AUDIO_SAMPLE_RATE_EXACT * 2^24;
Did you actually make this change? Because the original value of "44.1" is not the same as AUDIO_SAMPLE_RATE_EXACT.
 
Did you actually make this change? Because the original value of "44.1" is not the same as AUDIO_SAMPLE_RATE_EXACT.
Sorry I should have clarified. I made this value calculation more of a formula so it turned out like this:
Code:
feedback_accumulator = (uint32_t)((AUDIO_SAMPLE_RATE_EXACT * 16777216.0f) / 1000.0f);

I used the original library and just made the above change so the code is processing 44.1 kHz (AUDIO_SAMPLE_RATE_EXACT = 44100). Then I recompiled the flashed the Teensy and sent over a sine wave at 44.1 kHz:
Code:
speaker-test -D hw:2,0 -c 2 -t sine -f 1000 -r 44100

which works the same so I know this change was fine. Below is the audio sample it produced which looks normal:
1771266620331.png


Then I changed AUDIO_SAMPLE_RATE_EXACT to 48000 keeping the feedback_accumulator change and of course updating the descriptors, recompiled and flashed the new software over and sent over a sine wave at 48 kHz and Front_Center.wav.

After setting the rate manually in audacity for both tracks to 48 kHz, this is how the audio tracks look. The sine wav looks better
1771267539646.png


But when you zoom in and look at the sine wav you will see issues where it is not smooth.
1771267730110.png


And of course you can still hear the buzzing from just listening to both audio files. So long story short this is how I arrived at "feedback_accumulator" is likely 1 of many changes needed.
 
Have you tried the updated Teensy USB audio code? There’s an extensive thread, with the useful bits starting about here. Previous posts looked promising, but the effort fizzled out unfinished.

The stock USB code just about works for 44.1kHz stereo with 128-sample audio blocks, but as you’ve discovered doesn’t really lend itself to being tweaked in any way. The new code seems pretty robust, and the main author @alex6679 is as responsive as Real Life permits when a bug is found! I’ve contributed a few lines of code, some testing, and general cries of encouragement… Judging by the thread, there’s at least a few users out there, and they seem happy.

At the moment “installing” it is a bit convoluted, but it’s possible it’ll make it into Teensyduino at some stage. 1.60 should release soon, then it’s a matter of what Paul wants to include in 1.61.
I plugged the files in from this repo here. I then changed the "AUDIO_SAMPLE_RATE_EXACT" and "AUDIO_SAMPLE_RATE_I" to both 48000

I then recompiled/flashed over the new code to the Teensy and ran my same tests. Good news the results are as expected now:
Sound Screenshot for both Front_Center and Sine Using Alex Code at 48 kHz.png


The sine wav jitters a little at the beginning but I think that is expected. I read somewhere maybe in the repo or in that thread it does this but this is fine.
Sine Wav Detailed Screenshot With Jitter.png


Regardless, listening to the audio sounds fine; I don't hear the buzzing now. This issue has been solved! Huge shoutout to the work @alex6679 has done. Thanks everyone
 
Nice to hear that your problems with the USB interface have been solved. Thanks also to h4yn0nnym0u5e for helping me and contributing to the USB audio interface.

If you are not quite sure whether buffer over- or underruns occur even after startup, you could also use the getStatus function of the USB output:

C++:
    USBAudioOutInterface::Status status = usb1.getStatus();
    Serial.print("buffer overrun: ");
    Serial.println(status.usb_audio_overrun_count);
    Serial.print("buffer underruns: ");
    Serial.println(status.usb_audio_underrun_count);

You could e.g. request the status and print it every second. After a few over- or underruns at the start up, these number should not change anymore.
 
Nice to hear that your problems with the USB interface have been solved. Thanks also to h4yn0nnym0u5e for helping me and contributing to the USB audio interface.

If you are not quite sure whether buffer over- or underruns occur even after startup, you could also use the getStatus function of the USB output:

C++:
    USBAudioOutInterface::Status status = usb1.getStatus();
    Serial.print("buffer overrun: ");
    Serial.println(status.usb_audio_overrun_count);
    Serial.print("buffer underruns: ");
    Serial.println(status.usb_audio_underrun_count);

You could e.g. request the status and print it every second. After a few over- or underruns at the start up, these number should not change anymore.
That’s a great idea. I was seeing overrun in my scenario when I tested this. I’m going to make some tweaks. My clocks got out of sync. I speculate I need to make Teensy be master I2S and Pi is slave going off Teensy clocks. It’ll take me a little bit to restructure everything and test this idea, but this should be more stable.
 
Last edited:
Nice to hear that your problems with the USB interface have been solved. Thanks also to h4yn0nnym0u5e for helping me and contributing to the USB audio interface.

If you are not quite sure whether buffer over- or underruns occur even after startup, you could also use the getStatus function of the USB output:

C++:
    USBAudioOutInterface::Status status = usb1.getStatus();
    Serial.print("buffer overrun: ");
    Serial.println(status.usb_audio_overrun_count);
    Serial.print("buffer underruns: ");
    Serial.println(status.usb_audio_underrun_count);

You could e.g. request the status and print it every second. After a few over- or underruns at the start up, these number should not change anymore.
So quick update, I did in fact update my code so I made the Teensy be master clock and Pi slave to it. That seemed to resolve most of the overrun issues because now the overrun is not counting up towards infinity anymore. It is now just at 1 and constantly printing the below.
1771817045308.png


@alex6679 I think this is what you meant where it does it a little at start up but then stabilizes.
 
Also, I submitted a pull request here to improve the feedback_accumulator calculation as part of the discussion in this thread since I had found it and it might help the next person.
 
Back
Top