Sample rate conversion 352/384K to 192K Topic is solved

Sub forums for various specialist XMOS applications. e.g. USB audio, motor control and robotics.
Zed1970
Active Member
Posts: 55
Joined: Tue Oct 15, 2019 10:36 am

Sample rate conversion 352/384K to 192K

Post by Zed1970 »

Hi,

Our product (based on xCORE-200 Multichannel Audio Platform) only has two outputs, SPDIF optical and coaxial -there is no I2S whatsoever. The SPDIF optical runs up to 192k and the coaxial up to 352/384k. As the product advertises itself as 352/384k capable over USB we have an urgent requirement to convert 352/384k down to 192k so the user can still use the optical interface. Both interfaces need to at least render audio at all rates. The rate conversion files on xmos git appear to have a maximum rate of 192k, it is possible to up the maximum rate to 352/384k then convert down to 192k?

So cutting a long winded story short, we're after a 352/384k to 192k rate converter..

Thanks.


View Solution
User avatar
fabriceo
XCore Addict
Posts: 181
Joined: Mon Jan 08, 2018 4:14 pm

Post by fabriceo »

Hi

guess you are using the standard SPDIF_TX library ?
were you able to configure it to render 352/384k on coax ? ? ?
is the SDPIF task running on tile1 (like USB) or tile0 (like Audio I2S) ?
do you have 2 tasks and 2 one-bit port for the coax and the optical or just one for both ?

when it comes to SRC, I ve made one for my application, to down sample 352/384/176/192 to 88/96 respectively, with a pretty simple approach : moving average to divide by 4 or 2 and then FIR at 88/96 to remove as much aliasing as possible.

for FIR 88/96k on stereo, I went ok with 96 points on each channels (after the moving average), all that done in the Decouple Handler (interrupt) before sending data to Audio task (just after in fact, delayed by 1 sample).
for 176/192k, I ve made some tests and I got only 40 taps FIR for 2 channels driven in one task and then this is not enough to clean the signal.

So you may have to consider 2 specialized task (1 for left , 1 for right), receiving data from Decouple Handler via dedicated channels, processing Average by 2 and then 176/192k FIR 96 taps-ish. the output will directly drive the spdif channel.

the code for averaging is no brainer, and I took the code for FIR directly from the lib_dsp available on GitHub, no brainer also.

with same approach I also made FIR to decimate DSD Native and DoP signal , so XMOS-200 is quite capable :)

good luck, keep us posted
User avatar
akp
XCore Expert
Posts: 578
Joined: Thu Nov 26, 2015 11:47 pm

Post by akp »

Like fabriceo says if you can convert the 352 to 176 that will be substantially easier than converting it to 192 since it's just a synchronous decimate by two that will work for either 352 or 384 input. Otherwise you'll need a more sophisticated SRC which would still work but it won't be as simple.

I suspect you can do stereo FIR decimator by 2 relatively easily, but I'm not sure if you can do it in a single task with a 384k stereo input. I haven't tried it. fabriceo uses a hybrid approach of a short FIR then a longer FIR. What I wonder is, fabriceo, did you try dsp_filters_decimate ? That would seem to be the obvious solution rather than the hybrid since it should be optimized to not calculate the unneeded outputs.
User avatar
fabriceo
XCore Addict
Posts: 181
Joined: Mon Jan 08, 2018 4:14 pm

Post by fabriceo »

akp wrote: Mon Nov 09, 2020 3:09 pm What I wonder is, fabriceo, did you try dsp_filters_decimate ? That would seem to be the obvious solution rather than the hybrid since it should be optimized to not calculate the unneeded outputs.
Hi,
when you look in the code of dsp_filters_decimate, its basically a FIR but moving state data (Xn..) by group of N (decimation factor) instead of one by one. The overhead in the code is from my point of view too big compared to a basic average filter by N and then optimized FIR at FS. that said, rewriting the code with concept of circular buffer would be much better to avoid the data transfer of N sample at each cycle. to be investigated
Zed1970
Active Member
Posts: 55
Joined: Tue Oct 15, 2019 10:36 am

Post by Zed1970 »

Hi Fabriceo,

Thanks for you replay and apologies for the delay in responding; it been extremely busy. To answer your questions:
1) guess you are using the standard SPDIF_TX library ? Yes
2) were you able to configure it to render 352/384k on coax ? ? ? Yes, used the PLL to give 48-50MHz then used the 192/176 routines (SpdifTransmit_4)
is the SDPIF task running on tile1 (like USB) or tile0 (like Audio I2S) ? AUDIO_IO_TILE 0
do you have 2 tasks and 2 one-bit port for the coax and the optical or just one for both ? The same task does both. At the point where the hardware is feed both transceivers are written too i.e. SpdifTransmit_4().... partout(optical, 16, encoded_byte); partout(coaxial, 16, encoded_byte); I took this approach because to change from coax to optical it's just a pin tweak in the sample xk-audio-216-mc.xn meaning it uses the same code; so I just feed both outputs simultaneously. It's probably not the best but we are still learning..

So what would you suggest is the way forward regarding this down conversion challenge?

Thanks.
Zed1970
Active Member
Posts: 55
Joined: Tue Oct 15, 2019 10:36 am

Post by Zed1970 »

Hi,

I have now split out the optical and coaxial so each have their own core. The next step is the moving average as described above. How is this achieved?

Cheers.
Zed1970
Active Member
Posts: 55
Joined: Tue Oct 15, 2019 10:36 am

Post by Zed1970 »

Hi,

Any responses to the above questions? I don't want to start a new ticket just for them. So the I now need to know how to do this moving average divide as suggested. Even though termed as a no brainier I have no idea..
I only have to convert 352=>1176 or 384=>176 now, but that's probably going to be the same routine as I'll switch the PLL like in 44/48,176/192...

Cheers
User avatar
akp
XCore Expert
Posts: 578
Joined: Thu Nov 26, 2015 11:47 pm

Post by akp »

The easiest way to do a moving average by 4 is to right shift each of the four samples by 2 and then sum them.
Sample out = sample1 >> 2
Sample out += sample2 >> 2
Etc 2 more times to get a moving average by 4.

Edit: samples need to be signed 32 bit integers for this to work
Zed1970
Active Member
Posts: 55
Joined: Tue Oct 15, 2019 10:36 am

Post by Zed1970 »

LOL True! I was over majorly thinking the term. So far I have two cores one for coax and one for optical and both work and play audio at other rates. Based on the reference design optical and coaxial d-type output flip-flops are driven by a common clock. At 352/384 the PLL operates around 48MHz feeding both flip-flops. The coax side renders the 352/384 directly and the optical side just halves the input rate to 176/192. The optical side can render 176/192 with a 48MHz clock fine - tested; the data has been bit stretched to suit the x2 clock.
So the problem is back to averaging two samples of 352/384 resulting in a single 176/192 sample. As a test if I dump every second 352/384 sample it renders at 176/192 very nicely albeit missing data.. I've tried various was to average two samples but every time it results in nothing or very distorted audio. My suspicions are that the incoming samples are encoded making and basic +2 => /2 averaging useless. The outgoing optical side is encoded but that is done 'after' collecting the samples.

The sample collection within the demo code is: sample = inuint(c_tx0) >> 4 & 0x0FFFFFF0 ;
As stated earlier I've tried a number ways to get two samples and add/divide them to effectively halving the data rate but nothing works to date (including all the shifting and &0x..). So is the result from inuint() encoded making simple averaging fail?

Thanks.
Zed1970
Active Member
Posts: 55
Joined: Tue Oct 15, 2019 10:36 am

Post by Zed1970 »

Sorted! The key was akp's comment being 'signed'. Long handed version that works below. Before I close this ticket is the below code OK? I'm not going to FIR it as it's only a simple /2 unless popular opinion says so.. thoughts?

unsigned sample, sample2, control, preamble, parity;
signed Sample, Sample2;
...
/* Get L/R samples */
Sample = inuint(c_tx0);
Sample2 = inuint(c_tx0);
/* Test for new frequency */
if (testct(c_tx0))
{
chkct(c_tx0, XS1_CT_END);
return;
}
sample = inuint(c_tx0);
sample2 = inuint(c_tx0);

Sample = Sample + sample;
Sample = Sample >> 2;
sample = Sample >> 4 & 0x0FFFFFF0 ;

Sample2 = Sample2 + sample2;
Sample2 = Sample2 >> 2;
sample2 = Sample2 >> 4 & 0x0FFFFFF0 ;
Post Reply