如何为44100采样率立体声16位PCM数据实现无舍入误差的整数型简易IIR低通滤波器?
Got it, let's convert your existing floating-point low-pass filter to pure integer operations step by step—this will eliminate rounding noise and keep your waveform precision intact, while sticking to the exact filter logic you already understand.
First, Recap Your Filter Logic
Your floating-point code uses the standard first-order RC low-pass formula:
filtered = last_filtered + alpha * (input - last_filtered)
Which simplifies to:
filtered = alpha * input + (1 - alpha) * last_filtered
Where alpha is the coefficient calculated by your filter_freq function (a value between 0 and 1).
To convert this to integers, we'll use fixed-point arithmetic—we'll scale all values by a power of 2 (32768, since we're working with 16-bit ints) to turn decimals into integers, then scale back down at the end.
Step 1: Precompute Integer Coefficients
First, convert your floating-point alpha to an integer by scaling it by 32768 (2^15, which fits perfectly for Q15 fixed-point format):
#include <math.h> #define SCALE_FACTOR 32768 #define HALF_SCALE 16384 // For proper rounding INT32 calculate_alpha_int(DOUBLE cut_freq) { DOUBLE alpha_float = filter_freq(cut_freq); // Round to nearest integer to avoid truncation error return (INT32)(alpha_float * SCALE_FACTOR + 0.5); } // Keep your existing coefficient calculation function DOUBLE filter_freq(DOUBLE cut_freq) { DOUBLE a = 1.0/(cut_freq * 2 * M_PI); DOUBLE b = 1.0/44100.0; // Replace with your SAMPLE_RATE define if needed return b/(a+b); }
Step 2: Integer Filter Implementation
We'll use 64-bit integers for intermediate calculations to avoid overflow, and keep the filtered state in scaled integer form to preserve precision between samples.
void integer_low_pass_filter(INT16* input_buffer, INT16* output_buffer, UINT32 buffer_size, DOUBLE cut_freq) { // Initialize state variables (scaled by SCALE_FACTOR, stored as 64-bit to avoid overflow) INT64 last_filtered_left = 0; INT64 last_filtered_right = 0; // Precompute integer coefficients once outside the loop INT32 alpha = calculate_alpha_int(cut_freq); INT32 one_minus_alpha = SCALE_FACTOR - alpha; INT32 left_id = 0; INT32 right_id = 1; for(UINT32 a = 0; a < (buffer_size/2); ++a) { // Grab raw 16-bit input samples INT16 input_l = input_buffer[left_id]; INT16 input_r = input_buffer[right_id]; // Perform integer filter calculation (scaled by SCALE_FACTOR) // filtered = alpha*input + (1-alpha)*last_filtered INT64 filtered_l_int = (INT64)alpha * input_l + (INT64)one_minus_alpha * last_filtered_left; INT64 filtered_r_int = (INT64)alpha * input_r + (INT64)one_minus_alpha * last_filtered_right; // Update state with full-precision filtered value (don't scale back yet!) last_filtered_left = filtered_l_int; last_filtered_right = filtered_r_int; // Scale back to 16-bit int with proper rounding (add half the scale before shifting) INT16 output_l = (INT16)((filtered_l_int + HALF_SCALE) >> 15); INT16 output_r = (INT16)((filtered_r_int + HALF_SCALE) >> 15); // Optional: Add saturation to prevent overflow when accumulating to output INT32 temp_l = output_buffer[left_id] + output_l; INT32 temp_r = output_buffer[right_id] + output_r; output_buffer[left_id] = (temp_l > 32767) ? 32767 : (temp_l < -32768) ? -32768 : (INT16)temp_l; output_buffer[right_id] = (temp_r > 32767) ? 32767 : (temp_r < -32768) ? -32768 : (INT16)temp_r; // Move to next stereo sample pair left_id += 2; right_id += 2; } }
Key Details Explained
- Fixed-Point Scaling: By multiplying everything by 32768, we turn the 0-1 range of
alphainto a 0-32768 integer range. This lets us use pure integer multiplication/addition instead of floating-point operations. - 64-Bit Intermediate Values: Using
INT64for filtered state and calculations prevents overflow—since a 16-bit int multiplied by a 16-bit coefficient gives a 32-bit value, and adding two of those can exceed 32-bit limits. - Proper Rounding: Adding
HALF_SCALE(16384) before shifting right by 15 bits ensures we round to the nearest integer instead of truncating, which reduces quantization noise that causes pop sounds. - Saturation: The optional saturation step ensures we don't exceed the 16-bit int range when accumulating to the output buffer, which eliminates another common source of audio distortion.
Why This Fixes Your Pop Issue
Your original floating-point code introduced small rounding errors every time you converted between float and int16. These errors accumulate over samples and create audible pops. By keeping all calculations in integer form (with scaled state to preserve precision), we eliminate those floating-point rounding artifacts entirely.
内容的提问来源于stack exchange,提问作者1bytecharhaterxxx

