Audio Average 96kHz -> 48kHz

Status
Not open for further replies.
Hi,

my external adc TLV320AC6140 is sampling at 96kHz and my teensy audio is working at 48KHz.

If i set my ADC to sample the audio at 48kHz, everything works perfectly. Now changing the I2S master clock to 96kHz the audio sound is chopping.
I set a sine wave frequenz generator at 1khz in the ADC input and looked at the data on my MAC with an digital audio osciloscope and the sine wave has 0 values in 1ms length and here is my question:
What should i change in the audiostream class to make an average work so that i doesnt send a 128 zero value package?



Code:
void Filter_FFT::update(void)
{
  audio_block_t *block;
  block = receiveReadOnly();
  if (!block) return;

#ifdef AVG

 /* for (int i=0; i<AUDIO_BLOCK_SAMPLES; i++) 
  {
    audiodata[i] = float(block->data[i]);
  }
  
  //lowpass_average->process (AUDIO_BLOCK_SAMPLES, &audiodata);
  */
  for(int i=0; i<128;i=i+oversampling)
  {
    array_avg[countAVG] = block->data[i];
    for(int j=1; j<oversampling;j++)
    {
      array_avg[count] = (array_avg[count] + block->data[i+j])/2;
    }

    countAVG++;
  }
  
  if(countAVG >= 128)
  {
    for(int i=0; i<128;i++)
    {
      block->data[i] = (int16_t)array_avg[i];
    }
    countAVG=0;

    transmit(block);
    release(block);
    return;
  }
  else
  {
    release(block);
    return; 
  }
 
Are you changing AUDIO_BLOCK_SAMPLES? Presumably you've set AUDIO_SAMPLE_RATE_EXACT to 48000.0 ?
Are you expecting to run the Audio lib at 96 or 48kSPS?
Do you want good anti-aliasing performance above 20kHz? If so you'd need a halfband filter to decimate by 2
without aliasing ultrasonic components down to audible, otherwise the simple averaging of two samples will work,
but with some frequency response droop at HF.

Are you running the TLV320AC6140 at 96kSPS for a specific reason?
 
Hi MarkT,

i didnt change the AUDIO_BLOCK_SAMPLES and i left it as 128.
Yes i have changed the teensy core usb files to run at 48kHz and it works fine.
"Are you expecting to run the Audio lib at 96 or 48kSPS?" I want that the I2S and my custom class runs at 96kHz and the Audio lib with USB at 48kHz.

What do you mean here?
"Do you want good anti-aliasing performance above 20kHz? If so you'd need a halfband filter to decimate by 2
without aliasing ultrasonic components down to audible, otherwise the simple averaging of two samples will work,
but with some frequency response droop at HF."

The audio signal should be between 100Hz and 20kHz. My goal is to improve the SNR by averaging the audio signal.

I am running the TLV320AC6140 at 2x 48kHz to test the average mechanismus first.
I want to run the TLV320AC6140 later at 768kHz and make a decimation/average down to 48kHz.

So what i assume to be the reason is that the AudioStream class calls the "update()" when 128 AUDIO_BLOCK_SAMPLES arrived and my class Filter_FFT must always transmit and release the audio block to the USB otherwise it wont work.
The AudioOutputUSB class is reading "empty" AUDIO_BLOCK_SAMPLES when i do the average process. My class Filter_FFT is pausing the transmit by one block, so every second "update" call i transmit the new audio data.



Code:
AudioInputI2S           i2s;           
Filter_FFT              filter1(SAMPLERATE , OVERSAMPLING, true); 
Filter_FFT              filter2(SAMPLERATE , OVERSAMPLING, false); 
AudioOutputUSB          usb;   
        
AudioConnection          patchCord1(i2s, 0, filter1, 0);
AudioConnection          patchCord2(i2s, 1, filter2, 0);
AudioConnection          patchCord3(filter1, 0, usb, 0);
AudioConnection          patchCord4(filter2, 0, usb, 1);

In this code i understood that AudioInputI2S class call my class Filter_FFT for left and right channel via "update" function right?
When my class Filter_FFT release and transmit the new audio block, the AudioOutputUSB class gets called...

But if i wait for 2x 128 AUDIO_BLOCK_SAMPLES and then transmit the new audio block for the AudioOutputUSB class it bricks the data =/

I want that the AudioInputI2S and Filter_FFT class runs 2x faster than AudioOutputUSB, how can i fix this issue?

Thanks,

Michael
 
In your update(), you request a read-only block but once you have accumulated 128 averaged samples in array_avg, you then copy that over the read_only block. Try receiveWritable instead of receiveReadOnly.

Pete
 
You'll need the audio lib to run at 96kSPS from what you say, and have a bespoke version of AudioOutputUSB
that decimates its input by 2 to drive out at 48kSPS

Or just use 96kSPS throughout, I believe that works for USB?
 
Hi Pete, Hi MarkT,

i try receiveWritable instead of receiveReadOnly and unfortunately that didn't make any difference.
So here is a plot from 48kHz sine wave at 200Hz. The Audio and USB are running at 48kHz:
Screenshot 2021-04-29 at 06.56.20.png

If i set the Audio I2S to run at 96kHz and do the average it look like this:
Screenshot 2021-04-29 at 07.00.38.png

Setting the AUDIO_SAMPLE_RATE_EXACT at 96kHz and run everything at 96kHz works but my goal is to go up 768kHz and average it down to 48kHz.

Can it be the "transmit" function from AudioStream library that is causing this issue? Looking at the second plot, it looks like the USB transmitter reads an empty audio block.

// Transmit an audio data block
// to all streams that connect to an output. The block
// becomes owned by all the recipients, but also is still
// owned by this object. Normally, a block must be released
// by the caller after it's transmitted. This allows the
// caller to transmit to same block to more than 1 output,
// and then release it once after all transmit calls.
void AudioStream::transmit(audio_block_t *block, unsigned char index)

If transmit gets called by I2S then my code gets called and AudioOutputUSB?

Code:
AudioConnection          patchCord1(i2s, 0, filter1, 0);
AudioConnection          patchCord2(i2s, 1, filter2, 0);
AudioConnection          patchCord3(filter1, 0, usb, 0);
AudioConnection          patchCord4(filter2, 0, usb, 1);

What can i do to fix it?
 
Last edited:
In your Filter Decimation object, I would not re-use the input data blocks, but allocate a new one for output. This way you have better control on what is transmitted further.
you will then transmit a block at the reduced data rate.
 
If you sample at 96 kHz from the input and then reduce it to 48KHz in the output you have to read two input blocks for every update and output one block, or the special input code handles this. Anyway transmitting 96kHz data with a sample rate of 48kHz needs several blocks sent for every update and the receivers must have an input block buffer of the corresponding size.
 
Hi WMXZ,

yes i can allocate a new data block to control better the data.

Hi mlu,

how would i make the I2S input to send 2x AUDIO_BLOCK_SAMPLES via AudioStream to my Class?
 
That depends on the details of the input code, but I guess it should have two buffers each with space for 2x128 samples per channel.
The hardware uses dma to fill the buffers and when update is called for each channel you do something like:

Code:
/* one block of 128 samples */
    block = allocate();
    FILL_FIRST_BLOCK(block);
    transmit(block);
    release(block);
/* and a second block of 128 samples */
    block = allocate();
    FILL_SECOND_BLOCK(block);
    transmit(block);
    release(block);

Check the code of i2s input objects for details.

Every connected object must make sure to read two input blocks per update cycle or you will soon run out of audio memory.
 
Hi mlu,

i found the problem, the AudioOutputUSB::update always allocate a 128 zero buffer if there is no audio data block available.

I avoid it by adding "if(!right || !left)" and returning if there is no audio block. That fix made it possible =)
Not i am running at 96kHz from audio codec I2S and from there my Update() function gets called and i only transmit my buffer if my average is done.

Code:
void AudioOutputUSB::update(void)
{
	audio_block_t *left, *right;

	// TODO: we shouldn't be writing to these......
	//left = receiveReadOnly(0); // input 0 = left channel
	//right = receiveReadOnly(1); // input 1 = right channel
	left = receiveWritable(0); // input 0 = left channel
	right = receiveWritable(1); // input 1 = right channel
    
    if(!right || !left)// My Fix Here!
        return;


My average code look like this:

Code:
void Filter_FFT::update(void)
{
  audio_block_t *block;
  block = receiveWritable();//receiveReadOnly(); receiveWritable
  if (!block) return;

  for(int i=0; i<128;i=i+oversampling)
  {
    array_avg[countAVG] = block->data[i];
    
    for(int j=1; j<oversampling;j++)
    {
      array_avg[countAVG] = (int16_t)((int)(array_avg[countAVG] + block->data[i+j])/2);
    }

    countAVG++;
  }
  
  if(countAVG >= 128)
  {
  
    memcpy(&block->data[0],&array_avg[0],128 * sizeof(int16_t));
    
    countAVG=0;

    transmit(block);
    release(block);
    return;
  }
  else
  {
    release(block);
    return; 
  }
}
 
Code:
      array_avg[countAVG] = (int16_t)((int)(array_avg[countAVG] + block->data[i+j])/2);
This will only work if oversampling is 2.
You should first accumulate the number of samples in array_avg[countAVG] and then divide that by oversampling. Something like this:
Code:
    array_avg[countAVG] = block->data[i];

    for(int j=1; j<oversampling;j++)
    {
      array_avg[countAVG] += block->data[i+j];
    }
    array_avg[countAVG] /= oversampling;

array_avg will have to be declared as int32_t to avoid overflow.

Pete
 
Status
Not open for further replies.
Back
Top