Designing FIR filter for real-time decimation of audio on embedded system

I need to design a lowpass FIR filter to downsample an audio stream that is oversampled at 250Khz. The goal is to write a real-time audio player of an audio stream on an embedded system which is severely resource-constrained.

On this system, I don't have a DSP or FPGA available, it is a very slow CPU (MIPS 200Mhz) but with floating point instructions available. I'm familiar with code optimization, assembly and everything required to make sure the implementation is efficient, but I know almost nothing about filter design.

The output sampling rate for the stream should be 22050Hz (but anything in the range 20k-25k would do, because I am in control of the DAC clock so I can configure it to any value up to 48Khz -- but I'd rather stay in the 20-25K range of output sampling rate anyway unless this proves to be complex from a filter design standpoint).

I would also note that I don't need this system to produce high-fidelity audio like a CD; I would say that something that sounds like a low bitrate MP3 is OK. Just to give another reference point, I have already attempted to decimate my stream with factor M=11 by simply averaging each window of 11 consecutive samples, and I do get some aliasing as expected but it's not that bad -- it's not acceptable yet, but I was personally expecting much worse results for this very simple downsampling, while in reality it's actually pretty similar to the result I am looking for (though the aliasing is sometimes not acceptable).

By using an offline audio conversion tool on PC, I also verified that it's possible to downsample this audio stream to 22Khz and obtain audio quality that is good enough for my goals, and without aliasing -- I just need to understand how to do this in realtime on my embedded system, if possible.

The FIR filter must be very efficient because I'm doing real-time processing, so hopefully the number of taps should be quite small. I do not know right now what would be a target here: the fastest the best, because the system needs to do other processing in background. I was personally hoping something in the range of 10-20 taps, but honestly I have not benchmarked this yet. I would like to understand the compromises I get into and maybe running some experiments with different filters, to find a good balance between audio quality and performance.

I have close to zero understanding of the theory of signal processing, though I know something about the programming side of it (eg: if you give me the coefficients, I know how write a FIR filter).

Reading this page: https://dspguru.com/dsp/faqs/multirate/decimation/ I understand that I need to design this FIR filter first, and then decimate my stream.

I am thus attempting to create a decimation filter with M=11 (250000/11 ~= 22727, which is near to my desired output sampling rate). I've read other resources that tell me that it's better to use multistage cascading filters, but I would like to keep it simple for now, at least to get a basic understanding first.

The part that I cannot follow of that page is this:

The passband lower frequency is zero; the passband upper frequency is whatever information bandwidth you want to preserve after decimating. The passband ripple is whatever your application can tolerate.

The stopband lower frequency is half the output rate minus the passband upper frequency. The stopband attenuation is set according to whatever aliasing your application can stand. (Note that there will always be aliasing in a decimator, but you just reduce it to a negligible value with the decimating filter.)

As with any FIR, the number of taps is whatever is required to meet the passband and stopband specifications.

I have no clue how to convert these insights into a coefficient list I can use for the filter. I tried playing a bit with this simple online tool:

http://t-filter.engineerjs.com/

but I am not sure what numbers to input. This is where I got:

• Sampling freq: this is my input sampling freq, which is 250Khz.
• In the passband filter, I put "0 hz" in "From", but I don't know what to put in "To". The page linked above says "the passband upper frequency is whatever information bandwidth you want to preserve after decimating". Should this be 22050 Hz? I guess not, but I don't know.
• Passband ripple: the page says "whatever your application can tolerate". Since I don't have any background on this, I can't understand what it is a good valute for this.
• Stopband lower frequency: the page advises me to input half of the output rate minus the passband upper frequency. I can calculate this once I know what the passband upper frequency should be.
• Stopband upper frequency: the page speaks about "stopband attenuation", it doesn't mention "upper frequency". So I don't know what to input here.
• Stopband ripple: no explicit mention in the page, at least that I can understand.

Thank you in advance for any insights you can give me.

EDIT: as requested, I'm attaching a picture of the spectrum of an example stream:

• You didn't say what your downsampled rate was going to be? Is it going to be 50KHz (downsample by 5x). Or 41.6666KHz (downsample by 6x)? You want the stopband frequency to be $<=$ 2x the downsampled frequency, so that you don't get unwanted aliasing. – IanJ Jan 5 at 1:23
• I would recommend multistage decimations and polyphase filters. But as you are new in DSP, some MATLAB tools are quite helpful. BTW, what does "The output frequency for the stream is supposed to be 22050Hz" mean? Is 22050Hz the desired sampling rate or the highest frequency of the output stream? – ZR Han Jan 5 at 1:56
• I just edited and clarified that 22050Hz is the approximate desired sampling rate, so the decimation filter should be by 11x to fall near the 22050Hz target sampling rate. I think I'm confused about the question about the frequency of the output stream... I don't know. It's a audio stream that's meant to be heard by human ears, so I think it's should be within the hearing range. I see that the hearing range is 20Hz to 20Khz, but I think I will experiment with that. Let's say I want to try a highest frequency of 20Khz and then I can adjust that. – Giovanni Bajo Jan 5 at 8:00
• @GiovanniBajo I've never seen a system where "roughly approximate required rate" is any good (unless you're in control of a DAC clock yourself); and as you've already found out when using the filter designer, we're really missing a lot of the info to help you design this. If it's OK, I'll add a comment with a list of things that I think I would need to know to help you: $$\,$$ 1. What exactly is it that you're doing this on? Is it a PC-style CPU? A microcontroller? A DSP chip? An FPGA? A GPU? $$\,$$2. Where do your output samples go to? What's their purpose? – Marcus Müller Jan 5 at 9:30
• 3. What is the frequency content of your input signal? Is it maybe filtered already (If in doubt, add a picture of your input spectrum to your question!)? $$\,$$4. You say "must be very efficient", but I literally can't tell what that means; maybe it means "5000 taps or less is OK", maybe it means "impossible to do with an FIR, might need to use biquad sections". Can you please explain where that demand for efficiency comes from? What is the problem you see? $$\,$$ Please edit your question to answer as many of these questions as possible! – Marcus Müller Jan 5 at 9:34

Down-sampling is pretty straight forward in theory but difficult in practice. IN theory you just need to low-pass filter with a cutoff below half of the new sample rate and then you can just throw away the extra samples.

In practice, the choice of filter involves a lot of trade offs that are highly dependent on the specific requirements of your application, what properties you care about and what type of signals you are working with. Your spectrograms sure don't look like music or spoken word and there is an unusual amount of high frequency energy that you need to deal with effectively.

If I understand correctly you are fine with an integer down-sampling (11) and you are shooting for "acceptable auditory quality" for listening (and not analysis) purposes.

1. Passband: 0-10 kHz, gain 1, 1dB ripple
2. Stopband: 11kHz-125kHz, gain 0, -60dB ripple (which is 60 dB attenuation). The lower edge of your stop band should be half the final sample rate.

That will require 527 taps. For each output sample, advance your input by 11 samples and apply that filter. Since the filter is symmetric, it doesn't matter if you do a convolution or just a vector dot product.

Once you got this up and running (off line) and have verified that this works and meets your requirements, you can try to put it on the real time platform and tweak it.

If 527 taps is too much, you can adjust the following:

1. Lower the upper limit of the pass band (to 9KHz or 8 kHz). This will make it "duller"
2. Increase the stop band energy to maybe -50dB or -40dB. This will increase aliasing and sounds more distorted and "artificial".

Fuzz around until you have a good compromise between bandwidth, aliasing and number of taps.

The alternative would be to run an IIR filter, but that would have to be run at the higher sample rate so in terms of efficiency this might be a wash.

• Hello, thank you very much. Can you clarify the filter downsampling pass ("truncate or add zeros if needed")? If I divide the 527 coefficients into groups of 11, I end up with 47 groups of 11 coefficient plus one last group of 10 coefficients, but this won't make the filter symmetric, right? So I should probably split like this: 23 groups of 11, 1 group of 10, 1 coeff, 1 group of 10, 23 groups of 11. This makes of a total of 49 groups, that will generate 49 taps, right? Is this what you meant? – Giovanni Bajo Jan 5 at 16:23
• Sorry, I made a mistake. Filter down-sampling only applies if you have an upsampled signal, but that's not the case here. – Hilmar Jan 5 at 19:20