Audio Sample Rate: how to change from 44100 Hz (44117.65 Hz) to 48000?

Status
Not open for further replies.

Revalogics

Well-known member
So I have made a sketch for Teensy 3.6 I have that uses a single ADC as an input and USB as an output with nothing connected between them. I tested it using Windows and it worked OK, aside from faint RF noises I'm hearing so I put a 40nF ceramic capacitor between ADC input and AGND. I tried it on Linux (Ubuntu 16.04) using JACK audio connection toolkit, a realtime low-latency audio driver, and it worked OK for the first few seconds and gets noisy and noisy as time goes by and after a while, noise comes down gradually, going to clean audio again. Maybe it is caused by the inaccuracy of the sample rate?

I choose 48000 Hz because clock frequencies in Teensy can be divided and will arrive at 48000 accurately.

Anyone knows how to change the sample rate, especially for ADC, USB, and any other peripherals involved?

Code:
#include <Audio.h>

AudioInputAnalog adc1;
AudioOutputUSB usb1;
AudioConnection patchCord1(adc1, 0, usb1, 0);
AudioConnection patchCord2(adc1, 0, usb1, 1);

elapsedMillis fader13timer2;
byte fader13timer = 0;
byte fader13PWM = 0;
boolean dir = 0;

void setup() {
  AudioMemory(16);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
}

void loop() {
  fader13();
}

void fader13() {
  if(fader13timer == 0) digitalWrite(13, 1);
  if(fader13timer >= fader13PWM) digitalWrite(13, 0);
  if(fader13timer2 >= 5) {
    fader13timer2 = 0;
    if(dir) {
      fader13PWM++;
      if(fader13PWM == 255) dir = 0;
    }
    if(!dir) {
      fader13PWM--;
      if(fader13PWM == 0) dir = 1;
    }
  }
  fader13timer++;
}
 
See this thread
My code in message #44 in that thread has the setI2SFreq function (by @FrankB) which you can use to set the sampling frequency.
I think it is the current version. In theory all you need is that function and then call it in setup().
Code:
  setI2SFreq(48000);

Pete
 
I choose 48000 Hz because clock frequencies in Teensy can be divided and will arrive at 48000 accurately.

Anyone knows how to change the sample rate, especially for ADC, USB, and any other peripherals involved?
Though I haven't tried it you would need to change the PDB MOD register value in pdb.h (PDB_PERIOD). If you running at 48MHz bus then use (1000-1), there are probably many other things to change that I don't know about either especially with the USB audio interface.
 
This mod works fine but a question: How the bitcrusher object works now? The parameter hight parameter of freq is the setI2SFreq?
 
Haven't used this mod anymore because I found a good USB Audio interface that replaces Teensy for my audio I/O needs.

Looking at the sourcecode of bitcrusher, the bitcrusher.sampleRate(xsampleRate) function does the same thing, regardless of changes in sample rate using setI2Sfreq().
However, lowering the frequency using setI2Sfreq() achieves the same thing as reducing the sample rate in bitcrusher.

It's just my guess, maybe it works that way, maybe not.
 
Raising this up again, because I am trying the same thing with USB audio. I have modified all the references to 44100 clock to 48000, and I have all of the audio library objects working at this new sample rate except the AudioOutputUSB.

I have included the setI2SFreq(48000); code

In usb_desc.c, I have modified the lines:
LSB(44100), MSB(44100), 0, // tSamFreq

In usb_dev.c, I have modified the line

aaaa.PNG

and now the computer sets itself properly
usbaudiorate.PNG

I am Running a Teensy 3.6.

The computer is getting audio, but it sounds choppy and I suspect the sample rate is the issue.
 
Raising this up again, because I am trying the same thing with USB audio. I have modified all the references to 44100 clock to 48000, and I have all of the audio library objects working at this new sample rate except the AudioOutputUSB.

I have included the setI2SFreq(48000); code

In usb_desc.c, I have modified the lines:
LSB(44100), MSB(44100), 0, // tSamFreq

In usb_dev.c, I have modified the line

View attachment 11777

and now the computer sets itself properly
View attachment 11776

I am Running a Teensy 3.6.

The computer is getting audio, but it sounds choppy and I suspect the sample rate is the issue.

there are multiple places where you need to adjust
not only in usb_desc.cpp, usb_dev.cpp but also in usb_audio.cpp (look for "// TODO: dynamic adjust to match USB rate")
(I guess you must set target = 48)
 
there are multiple places where you need to adjust
not only in usb_desc.cpp, usb_dev.cpp but also in usb_audio.cpp (look for "// TODO: dynamic adjust to match USB rate")
(I guess you must set target = 48)

I did this:

Code:
// Called from the USB interrupt when ready to transmit another
// isochronous packet.  If we place data into the transmit buffer,
// the return is the number of bytes.  Otherwise, return 0 means
// no data to transmit
unsigned int usb_audio_transmit_callback(void)
{
	static uint32_t count=5;
	uint32_t avail, num, target, offset, len=0;
	audio_block_t *left, *right;

	if (++count < 9) {   // TODO: dynamic adjust to match USB rate
		target = 48;
	} else {
		count = 0;
		target = 45;
	}


Changing this made it sound even more choppier.
 
No, that's definitely wrong. You need to send 48 samples on EVERY frame, not just some of them.

Just in case it wasn't clear, that code is responsible for sometimes sending 45 samples, but usually sending 44 samples. Done 1000 times per second, the net result turns out to be close to 44117 samples/sec.

You also need to edit the max packet size in the descriptors. Even though to told it the sample rate in the audio descriptors, you also need to get that detail right so the host knows how big the arriving packets are supposed to be. It doesn't compute that for you, so you must put it in the endpoint descriptors too.

If you're using the input object, you also need to deal with the sample rate feedback endpoint, to tell the host to slightly speed up or slow down, so you get the right amount of data arriving.
 
No, that's definitely wrong. You need to send 48 samples on EVERY frame, not just some of them.

Just in case it wasn't clear, that code is responsible for sometimes sending 45 samples, but usually sending 44 samples. Done 1000 times per second, the net result turns out to be close to 44117 samples/sec.

You also need to edit the max packet size in the descriptors. Even though to told it the sample rate in the audio descriptors, you also need to get that detail right so the host knows how big the arriving packets are supposed to be. It doesn't compute that for you, so you must put it in the endpoint descriptors too.

If you're using the input object, you also need to deal with the sample rate feedback endpoint, to tell the host to slightly speed up or slow down, so you get the right amount of data arriving.

Thanks a lot!!!!!

Yes it seemed a little confusing... but finally I got it:

in usb_desc.h

Code:
 #define AUDIO_TX_SIZE         180
was changed to
Code:
 #define AUDIO_TX_SIZE         192

because usb_audio_transmit_callback() returns (target * 4); , and


The audio is perfect (with some very tiny buzzing) now.
 
Last edited:
Just for anyone curious, I now get perfect audio. These were my modifications:

In usb_audio.cpp:
Code:
unsigned int usb_audio_transmit_callback(void)
{
	uint32_t avail, num, target = 48, offset, len=0;
	audio_block_t *left, *right;  

	while (len < target) { // fill the buffer
		num = target - len;
		left = AudioOutputUSB::left_1st;
		if (left == NULL) {
			// buffer underrun - PC is consuming too quickly
			memset(usb_audio_transmit_buffer + len, 0, num * 4); 
			break;
		}
		right = AudioOutputUSB::right_1st;
		offset = AudioOutputUSB::offset_1st;

		avail = AUDIO_BLOCK_SAMPLES - offset;
		if (num > avail) num = avail;

		copy_from_buffers((uint32_t *)usb_audio_transmit_buffer + len,
			left->data + offset, right->data + offset, num);
		len += num;
		offset += num;
		if (offset >= AUDIO_BLOCK_SAMPLES) {
			AudioStream::release(left);
			AudioStream::release(right);
			AudioOutputUSB::left_1st = AudioOutputUSB::left_2nd;
			AudioOutputUSB::left_2nd = NULL;
			AudioOutputUSB::right_1st = AudioOutputUSB::right_2nd;
			AudioOutputUSB::right_2nd = NULL;
			AudioOutputUSB::offset_1st = 0;
		} else {
			AudioOutputUSB::offset_1st = offset;
		}
	}
	return target * 4;
}

In usb_desc.h:
Code:
#define AUDIO_TX_SIZE         192


In usb_desc.c:
Code:
LSB(48000), MSB(48000), 0,		// tSamFreq


In usb_dev.c:
Code:
 case 0x81A2: // GET_CUR (wValue=0, wIndex=interface, wLength=len)
		if (setup.wLength >= 3) {
			reply_buffer[0] = 48000 & 255; 
			reply_buffer[1] = 48000 >> 8; 
			reply_buffer[2] = 0;
			datalen = 3;
			data = reply_buffer;
		} else {
			endpoint0_stall();
			return;
		}
		break;
 
Nice stuff. I was planning on doing this too.

For what I remember from when I did the USB audio descriptors back in 2013, the best option would be to declare a series of different USB configuration descriptors for each sampling frequency (44.1, 48, 88.2, 96, 192); in this way the PC can "ask" the Teensy to reconfigure itself to support that frequency. This would also need some callbacks to modify the sampling frequency in the Audio lib dynamically, as well as resetting the I2S bus in case it's used.
 
the best option would be to declare a series of different USB configuration descriptors for each sampling frequency (44.1, 48, 88.2, 96, 192)


So yeah, I tackled this for the whole afternoon and it kinda works. I say "kinda" because now the Teensy shows two possible configurations (one for 44.1 and one for 48 KHz), it enumerates properly, it switches configuration properly without dying or crashing the host in the process, but I tried the easiest USB Passthrough to see if any sound output through the Teensy Audio USB output would come back through the Teensy Audio USB input aaaaaand it doesn't.

Instead of copy-pasting all the mods I did, I'm attaching a zip file with the modified USB files, while explaining what I did:

View attachment usb_audio.zip

  • First thing was to add a second alternate configuration for both AudioStreaming TX and RX interfaces, increasing the audio interfaces descriptor size accordingly (in usb_desc.c).
  • I had to drop the AUDIO_*X_SIZE defines and replace them with two separate defines, AUDIO_*X_SIZE_44_1_KHZ and AUDIO_*X_SIZE_48_KHZ (in usb_desc.h); these are used all over the USB core files.
  • The usb_audio_xxx_buffer[AUDIO_*X_SIZE/2] have been changed to usb_audio_xxx_buffer[AUDIO_*X_SIZE_48_KHZ/2]; since they are declared as aligned DMA buffers I'd rather initialize them to the maximum needed size and use a lesser part of it when switching to 44.1 KHz (in usb_audio.cpp)
  • usb_audio_transmit_callback now checks wether the current interface setting is for 44.1 or 48 KHz, and sets the uint32_t target accordingly (in usb_audio.cpp)
  • usb_setup() now initializes the BDT space for the RX endpoint to the bigger needed size (192 samples for the 48 KHz case) (in usb_dev.c)
  • when the Teensy receives a Set Interface request to change sampling frequency, it switches both interfaces to the newly requested frequency (in usb_dev.c)
  • I updated the GET_CUR request to show which sampling frequency is currently used (in usb_dev.c)
  • usb_isr(): when the routine reinitializes the BDT it checks which configuration is used to set the packets size accordingly

I yet have to understand which of these mods is affecting the data throughput of the AudioOutputUSB object (and I yet have to test this with I2S objects too) but it's a beginning.
 
I found another way, which takes less space; I forgot that you can say how many sampling frequency an AudioStreaming endpoint supports directly in its associated Audio Format descriptor.
As in the previous post, the Teensy enumerates properly, responds properly to the requests, changes sampling frequency etc, but still I can't get any output from the Teensy to the USB host; it's just a bunch of 0s.

I added a usb_audio_sampling_frequency value which, guess what, holds the current sampling frequency used by the USB Audio function, and a usb_audio_change_sampling_frequency flag that's used to see if the Teensy received a request to change its USB Audio function sampling frequency; I'm using this value to empty the USB Audio function buffers in usb_audio.cpp in hope that this would fix the no audio issue but it doesn't work.

Anyway, if you want to check it out and weigh in here's the mods: View attachment usb_audio.zip

edit: I forgot to mention that with this method (using the set endpoint control requests, instead of using the set interface alternate setting) the Teensy takes around 5 seconds to respond to the change of sampling frequency
 
THis is Awesome! I am about to test this right away.

Quick question, in:

Code:
int usb_audio_set_feature(void *stp, uint8_t *buf) 
{
	struct setup_struct setup = *((struct setup_struct *)stp);
	if (setup.bmRequestType==0x21) { // should check bRequest, bChannel and UnitID
			if (setup.bCS==0x01) { // mute
				if (setup.bRequest==0x01) { // SET_CUR
					AudioInputUSB::features.mute = buf[0]; // 1=mute,0=unmute
					AudioInputUSB::features.change = 1;
					return 1;
				}
			}
			else if (setup.bCS==0x02) { // volume
				if (setup.bRequest==0x01) { // SET_CUR
					AudioInputUSB::features.volume = buf[0] + (buf[1]<<8);
					AudioInputUSB::features.change = 1;
					return 1;
				}
			}
	}else if (setup.bmRequestType==0x22) {
			if (setup.bCS==0x01) { // sampling frequency
				if (setup.bRequest==0x01) { // SET_CUR
					uint32_t tmp = buf[0] + (buf[1]<<8) + (buf[2]<<16);
					if (tmp!=44100||tmp!=48000) return 0;
					usb_audio_sampling_frequency = tmp;
					usb_audio_change_sampling_frequency=true;
					return 1;
				}
			}
	}
	return 0;
}

It is possible to get this callback(usb_audio_change_sampling_frequency) bubble all the way up to our main code?
 
THis is Awesome! I am about to test this right away.3

Quick question, in:

Code:
int usb_audio_set_feature(void *stp, uint8_t *buf) 
{
	struct setup_struct setup = *((struct setup_struct *)stp);
	if (setup.bmRequestType==0x21) { // should check bRequest, bChannel and UnitID
			if (setup.bCS==0x01) { // mute
				if (setup.bRequest==0x01) { // SET_CUR
					AudioInputUSB::features.mute = buf[0]; // 1=mute,0=unmute
					AudioInputUSB::features.change = 1;
					return 1;
				}
			}
			else if (setup.bCS==0x02) { // volume
				if (setup.bRequest==0x01) { // SET_CUR
					AudioInputUSB::features.volume = buf[0] + (buf[1]<<8);
					AudioInputUSB::features.change = 1;
					return 1;
				}
			}
	}else if (setup.bmRequestType==0x22) {
			if (setup.bCS==0x01) { // sampling frequency
				if (setup.bRequest==0x01) { // SET_CUR
					uint32_t tmp = buf[0] + (buf[1]<<8) + (buf[2]<<16);
					if (tmp!=44100||tmp!=48000) return 0;
					usb_audio_sampling_frequency = tmp;
					usb_audio_change_sampling_frequency=true;
					return 1;
				}
			}
	}
	return 0;
}

It is possible to get this callback(usb_audio_change_sampling_frequency) bubble all the way up to our main code?

I guess it is already visible; the usb_audio_sampling_frequency value is defined in usb_audio.h as an extern value and declared in usb_audio.cpp so as long as the USB Audio mode is selected that should work. Oh, please note that I only modified the part relative to the Audio only USB mode in usb_desc.h, any other mode should not compile; in that case you just need to replace the AUDIO_*X_SIZE defines with the new, frequency-dependent values.
 
Yes, I got the errors and added the defines.

I am checking the value in loop() but it never changes.
Code:
  if (sampling_frequency != usb_audio_sampling_frequency)
  {
    sampling_frequency = usb_audio_sampling_frequency;
    Serial.printf("NEW SamFreq %d", sampling_frequency);
  }

I'll keep playing around with it.. I will try to receive this value and then re-configure the I2S clocks to match.
 
it wierd, but this is the culprit:
Code:
	if ((tmp != 44100) || (tmp != 48000))
					return 0;

I am not sure why it fails there, but the I printed the value of tmp: and see it changing:

TEmp F: 48000
SamFreq: 44100

TEmp F: 48000
SamFreq: 44100

TEmp F: 44100
SamFreq: 44100
 
Maybe there's something wrong in the set feature function. Checking this right away.

Silly me:

Code:
if (tmp!=44100||tmp!=48000) return 0;

this should be if (tmp!=44100&&tmp!=48000) return 0;

:D

This was also the reason why the teensy would hang for 5 seconds between any frequency change requested by the host.

Latest version is in this attachment (I also conveniently modified the other parts of usb_desc.h): View attachment usb_audio.zip
 
Yes. In fact after I get the callback, I set the new MCLK and set the new sample rate to my I2S Ics and the audio resumes on the PC:

Code:
void audioformat_change() // change sample rate requests
{
  Serial.println("audioformat_change_callback");

  dac.muteAudio(1, 1);
  RADIOMODES mode = radio.mode;
  Serial.printf("New samplerate: %d\n", usb_audio_sampling_frequency);
  recorder.halt(); // stop everything here
  setupI2SCLOCKS(usb_audio_sampling_frequency);
  radio.audio_sample_rate = usb_audio_sampling_frequency;
  radio.prop(P_DIGITAL_IO_OUTPUT_SAMPLE_RATE, usb_audio_sampling_frequency);
  //  if (mode != RADIO_OFF)
  //    radio.powerUp();
  dac.muteAudio(0, 0);
  audio_rate_change = false;
}
 
Strangely, after I sent this comment, I do not see the device appear anymore... So AudioOutputUSB may be broken.
 
Status
Not open for further replies.
Back
Top