Цифровой БИХ фильтр (IIR filter). Разработка класса.
on 12.07.2012 at 23:03В этой теме в доступной форме будет показана практическая работа с так называемыми фильтрами с бесконечной импульсной характеристикой (БИХ фильтры), или же infinite impulse response (IIR) в английском варианте. Стоит отметить, что работа с данным видом фильтров обладает магией эмпирики, т. е. для того чтобы получить фильтр с адекватными параметрами все же приходится обращаться за помощью к эксперименту. Дело в том, что данный вид фильтра может иметь столь неприятное явление как нестабильность. Например, фильтр работал отлично пару первых секунд, а потом вдруг сбесился. Это ужасно неприятно. Конечно же магии в этом явлении нет. Всё довольно просто объясняется с помощью математики. Мы углубляться в детали пока не будем. Не хочется томить читателя бесконечным количеством формул. Откуда же взялся IIR фильтр? Кто его придумал? Каков его принцип работы? Да, это именно те вопросы, которые меня беспокоили в то время, когда я только начал разбираться в этом типе фильтров. Все банально. Кто хоть чуть-чуть разбирается в аналоговых фильтрах (из схемотехники) может вспомнить такие фильтры как RC (resistor-capasitor или же Low-Pass Filter), CR (capasitor-resistor или же High-Path Filter), LC (inductance-capasitor или резонансный контур), а также их производные. Во времена, когда человечество начало осваивать дискретные методы обработки сигналов, было осуществлено немало попыток «портировать» хорошо зарекомендовавшие себя аналоговые фильтры в цифровой вид фильтров. Эти самые попытки и дали изначальный толчок для развития IIR фильтров. Тому, кто хочет разобраться в технике преобразования аналогового фильтра в цифровой его аналог, придется изучить азы так называемого Z-преобразования и еще много чего другого (в том числе и электроники). Конечно же, это большой плюс, если вы будете в совершенстве понимать как работает тот или иной фильтр, но этого можно добиться полагаясь на достижения математиков и людей работающих в сфере цифровой обработки сигналов. К чему это я? Да к тому, что к нашему счастью большое количество IIR фильтров уже отточено до совершенства и «изобретать велосипед» не приходится. Все! Только что я взглянул на количество набранного текста и понял, что сказано много, но толком ничего. Поэтому перехожу к практике. Итак, ниже я покажу как работать с такими фильтрами как:
- LPF (Low-Pass filter);
- HPF (High-Pass filter);
- BPF (Band-Pass filter);
- BSF (Band-Stop или NOTCH filter);
- EQF (Equalizer filter);
- LSF (Low-Shelf filter);
- HSF (High-Shelf filter).
Как отфильтровать сигнал. Фильтруется сигнал по следующей формуле:
y[n] = (b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2])/a0
Где
y[n] – выходной (отфильтрованный) сигнал;
x() – входной сигнал, который нужно отфильтровать;
b0-b2 и a0-a2 – коэффициенты, которые рассчитываются для конкретного фильтра (LPF, HPF…).
Пояснение. x[n] – текущая выборка, x[n-1] – предыдущая выборка.
Low-Pass filter. Этот фильтр работает следующим образом. Ниже определенной частоты этот фильтр пропускает сигнал, все остальное «обрезает». Расчет коэффициентов данного фильтра осуществляется с помощью формул:
b0 = (1 - cos)/2
b1 = 1 - cos
b2 = (1 - cos)/2
a0 = 1 + alpha
a1 = -2*cos
a2 = 1 – alpha
Как рассчитываются недостающие параметры, я напишу ниже.
High-pass filter. Этот фильтр работает по абсолютно противоположному принципу к LPF, т.е. пропускает все выше определенной частоты, а все остальное «обрезает». Расчет коэффициентов данного фильтра осуществляется с помощью формул:
b0 = (1 + cos)/2
b1 = -(1 + cos)
b2 = (1 + cos)/2
a0 = 1 + alpha
a1 = -2*cos
a2 = 1 - alpha
Как рассчитываются недостающие параметры, я напишу ниже.
Band-Pass filter. Принцип работы следующий. Фильтр пропускает только то, что находится в пределах полосы пропускания заданной при расчете фильтра. Расчет коэффициентов:
b0 = alpha
b1 = 0
b2 = -alpha
a0 = 1 + alpha
a1 = -2*cos
a2 = 1 - alpha
Как рассчитываются недостающие параметры, я напишу ниже.
Band-Stop filter. Принцип его работы следующий. Фильтр пропускает только то, что находится вне полосы пропускания, заданной при расчете фильтра. Расчет коэффициентов фильтра:
b0 = 1
b1 = -2*cos
b2 = 1
a0 = 1 + alpha
a1 = -2*cos
a2 = 1 - alpha
Equalizer filter. Этот вид фильтра является очень хорошим для построения цифрового эквалайзера. Дело в том, что с помощью этого фильтра можно как фильтровать определенную полосу частот, так и подымать (усилить) ее. Расчет коэффициентов осуществляется с помощью следующих формул:
b0 = 1 + alpha*A
b1 = -2*cos
b2 = 1 - alpha*A
a0 = 1 + alpha/A
a1 = -2*cos
a2 = 1 - alpha/A
Low-Shelf filter. Этот фильтр оставляет полосу частот которая находится ниже указанной при расчете фильтра частоты, все остальные частоты он ослабляет по амплитуде на указанную при расчете фильтра величину. Расчет коэффициентов:
b0 = A*[ (A+1) - (A-1)*cos + beta*sin ]
b1 = 2*A*[ (A-1) - (A+1)*cos ]
b2 = A*[ (A+1) - (A-1)*cos - beta*sin ]
a0 = (A+1) + (A-1)*cos + beta*sin
a1 = -2*[ (A-1) + (A+1)*cos ]
a2 = (A+1) + (A-1)*cos - beta*sin
High-Shelf filter. Этот фильтр оставляет полосу частот которая находится выше указанной при расчете фильтра частоты, все остальные частоты он ослабляет по амплитуде на указанную при расчете фильтра величину. Расчет коэффициентов:
b0 = A*[ (A+1) + (A-1)*cos + beta*sin ]
b1 = -2*A*[ (A-1) + (A+1)*cos ]
b2 = A*[ (A+1) + (A-1)*cos - beta*sin ]
a0 = (A+1) - (A-1)*cos + beta*sin
a1 = 2*[ (A-1) - (A+1)*cos ]
a2 = (A+1) - (A-1)*cos - beta*sin
Расчет недостающих параметров. Для расчета параметров используются следующие формулы:
A = sqrt[ 10^(dBgain/20) ]
// (только для полософормирующих фильтров) A = 10^(dBgain/40)
omega = 2*pi*frequency/sampleRate
sin = sin(omega)
cos = cos(omega)
// (если задана Q) alpha = sin/(2*Q) =
// (если задана полоса) alpha = sin*sinh[ ln(2)/2 * bandwidth * omega/sin ]
Отношение между шириной полосы и Q:
// (для ЦФ на основе БЛП) 1/Q = 2*sinh[ln(2)/2*bandwidth*omega/sin]
// (для аналогового прототипа) 1/Q = 2*sinh[ln(2)/2*bandwidth]
// (только для фильтров-ступенек) beta = sqrt(A)/Q
// (если задан наклон горки) beta = sqrt(A)*sqrt[ (A + 1/A)*(1/S - 1) + 2 ]
beta = sqrt[ (A^2 + 1)/S - (A-1)^2 ]
Отношение между наклоном и Q:
1/Q = sqrt[(A + 1/A)*(1/S - 1) + 2]
IIR фильтр, разработка кода. Так как представленная выше информация может показаться читателю сугубо абстрактной, непонятной и теоретической (хотя формулы и практические) сочту правильным показать реальную разработку класса фильтра. Сразу же оговорим, что из себя должен представлять этот класс. Это должен быть класс, создав объект которого можно спокойно и бесперебойно обрабатывать (фильтровать) непрерывный поток звуковых данных. Т. е. берем блок начальных данных (например звука, сигнала из датчика, микрофона…), обрабатываем его с помощью созданного класса и получаем обработанные данные. В общих чертах это пока все. Итак, разработка!
В первую очередь создаем хидер-файл, в котором мы и продумаем архитектуру класса. Сразу же оговорюсь, что работать мы будем с данными, которые относятся к данным с плавающей точкой. Так как фильтр может работать с разными типами данных типа float или double то удобно сразу же создать template класс (кто не совсем дружит с данным термином, template «спасает» нас от создания нескольких почти одинаковых классов, которые реально отличались бы в большинстве случаев типом переменных). Ниже представлен уже готовый и рабочий код написанный мной ранее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | #pragma once #include "math.h" #include "common.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* !M_PI */ #ifndef M_LN2 #define M_LN2 0.69314718055994530942 #endif /* !M_LN2 */ template <typename T> class CFilterIIRStream { public: CFilterIIRStream(void); ~CFilterIIRStream(void); typedef enum { LPF, //low-pass filter HPF, //high-pass filter BPF, //band-pass filter BSF, //band-stop filter NOTCH, //band-stop filter PEAKING_EQ, //equalizer filter LOW_SHELF, HIGH_SHELF, NONE } FILTERTYPE; ReturnCode Init( FILTERTYPE ft, float f0, float sr, float Q, float dBFilterGain); ReturnCode Init( FILTERTYPE ft, float w0, float bw = 0, float dBFilterGain = 0); // it is inplace filter! ReturnCode Process(T* pBuffIn, long BuffInLength); ReturnCode Flush(); const T* GetFilteredData(void); long GetFilteredDataLength(void); int GetFilteredDataLatency(void); // this option should be used if the blocks from // snd file passing in the reverse order (blockN, // block(N-1),..., Block1). This can be used if // we want to get linear phase response. To do // that we need to process sound in forward order, // then in reversed order. bool SetBackProcessing(bool bState) { return m_bIsBackProcessing = bState; }; bool GetBackProcessing() { return m_bIsBackProcessing; }; protected: void ApplyFilter(T* indata, long numSampsToProcess); bool m_bIsInitialized; bool m_bIsBackProcessing; T* m_pFilteredData; long m_FilteredDataLength; int m_Latency; T m_a0, m_a1, m_a2, m_b0, m_b1, m_b2; T m_x1, m_x2; T m_y1, m_y2; FILTERTYPE m_ft; }; |
Как вы наверное и сами понимаете, тут представлена всего лишь описательная часть кода т.е. реализация здесь отсутствует. Но я же все просто так не брошу! Вот и файл реализации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | /* All rights reserved. * */ /** * * Author: Viktor Signaievkyi */ #include "stdafx.h" #include "FilterIIRStream.h" /** Class constructor. */ template CFilterIIRStream::CFilterIIRStream(void) { m_bIsInitialized = false; m_pFilteredData = NULL; m_FilteredDataLength = 0; m_Latency = 0; m_a0 = 0; m_a1 = 0; m_a2 = 0; m_b0 = 0; m_b1 = 0; m_b2 = 0; m_x1 = 0; m_x2 = 0; m_y1 = 0; m_y2 = 0; m_ft = NONE; m_bIsBackProcessing = false; } /** Class destructor. */ template CFilterIIRStream::~CFilterIIRStream(void) { Flush(); } /** Initializing/ReInitializing IIR filter by necessary parameters. \param ft - filter type (Low-Pass, High-Pass, Band-Pass and Band-Stop...). \param f0 - decision frequency. Value between 0.0 and sampling frequency divided by 2. \param sr - sampling rate (frequency). \param Q - quality factor (f0/bandwidth). \param dBFilterGain - used only in case of PEAKING_EQ, LOW_SHELF and HIGH_SHELF filters. \Note: \LPF, HPF, BPF, BSF, NOTCH - use f0, sr, Q; \PEAKING_EQ - uses f0, sr, Q, dBFilterGain; \LOW_SHELF, HIGH_SHELF - use f0, sr, dBFilterGain. */ template ReturnCode CFilterIIRStream::Init(FILTERTYPE ft, float f0, float sr, float Q, float dBFilterGain) { T A, omega, sine, cosine, alpha, beta; m_ft = ft; A = pow(10, dBFilterGain /40); //= sqrt(pow(10, dBFilterGain /20)); omega = (T)(2 * M_PI * f0 /sr); sine = sin(omega); cosine = cos(omega); alpha = sine/(2*Q); beta = sqrt(A + A); //beta = sqrt(A)/Q; //TODO : Check beta correctness switch(ft) { case LPF: { m_b0 = (1 - cosine)/2; m_b1 = 1 - cosine; m_b2 = (1 - cosine)/2; m_a0 = 1 + alpha; m_a1 = -2*cosine; m_a2 = 1 - alpha; } break; case HPF: { m_b0 = (1 + cosine)/2; m_b1 = -(1 + cosine); m_b2 = (1 + cosine)/2; m_a0 = 1 + alpha; m_a1 = -2*cosine; m_a2 = 1 - alpha; } break; case BPF: { m_b0 = alpha; m_b1 = 0; m_b2 = -alpha; m_a0 = 1 + alpha; m_a1 = -2*cosine; m_a2 = 1 - alpha; } break; case BSF: case NOTCH: { m_b0 = 1; m_b1 = -2 * cosine; m_b2 = 1; m_a0 = 1 + alpha; m_a1 = -2 * cosine; m_a2 = 1 - alpha; } break; case PEAKING_EQ: { m_b0 = 1 + (alpha * A); m_b1 = -2 * cosine; m_b2 = 1 - (alpha * A); m_a0 = 1 + (alpha /A); m_a1 = -2 * cosine; m_a2 = 1 - (alpha /A); } break; case LOW_SHELF: { m_b0 = A * ((A + 1) - (A - 1) * cosine + beta * sine); m_b1 = 2 * A * ((A - 1) - (A + 1) * cosine); m_b2 = A * ((A + 1) - (A - 1) * cosine - beta * sine); m_a0 = (A + 1) + (A - 1) * cosine + beta * sine; m_a1 = -2 * ((A - 1) + (A + 1) * cosine); m_a2 = (A + 1) + (A - 1) * cosine - beta * sine; } break; case HIGH_SHELF: { m_b0 = A * ((A + 1) + (A - 1) * cosine + beta * sine); m_b1 = -2 * A * ((A - 1) + (A + 1) * cosine); m_b2 = A * ((A + 1) + (A - 1) * cosine - beta * sine); m_a0 = (A + 1) - (A - 1) * cosine + beta * sine; m_a1 = 2 * ((A - 1) - (A + 1) * cosine); m_a2 = (A + 1) - (A - 1) * cosine - beta * sine; } break; default: { } } // precompute the coefficients m_b0 /= m_a0; m_b1 /= m_a0; m_b2 /= m_a0; m_a1 /= m_a0; m_a2 /= m_a0; // zero in/out initial samples m_x1 = 0; m_x2 = 0; m_y1 = 0; m_y2 = 0; m_bIsInitialized = true; return RET_OK; } /** Initializing/ReInitializing IIR filter by necessary parameters. \param ft - filter type (Low-Pass, High-Pass, Band-Pass and Band-Stop...). \param w0 - decision frequency (in radians from 0 to pi). \param bw - bandwidth in radians (from 0 to pi). It should be choosen dep on ft and w0. \param dBFilterGain - used only in case of PEAKING_EQ, LOW_SHELF and HIGH_SHELF filters. \Note: \LPF, HPF - use only w0; \BPF, BSF, NOTCH - use w0 and bw; \PEAKING_EQ - uses w0, bw and dBFilterGain; \LOW_SHELF, HIGH_SHELF - use w0 and dBFilterGain. */ template ReturnCode CFilterIIRStream::Init(FILTERTYPE ft, float w0, float bw, float dBFilterGain) { m_ft = ft; // let assign to the sampling some dummy but fixed value float sr = (float)(2*M_PI); // exclude mistakes and exception situations w0 = abs(w0); bw = abs(bw); // analyzing and fixing improper coefficients, if possible switch(ft) { case LPF: { // automatically attenuating bandwidth bw = w0; } break; case HPF: { // automatically attenuating bandwidth bw = sr/2 - w0; } break; case BPF: case BSF: case NOTCH: { // we are out of upper possible frequency range if((w0 + bw/2) > sr/2) { // it is unpossible to set up bandwidth which is out of max frequency return RET_INVALID_ARG; } // we are out of lower possible frequency range else if((w0 - bw/2) < 0) { // it is unpossible to set up bandwidth which is out of min frequency (0) return RET_INVALID_ARG; } } break; case PEAKING_EQ: { } break; case LOW_SHELF: { } break; case HIGH_SHELF: { } break; default: { } } return Init(ft, w0, sr, w0/bw, dBFilterGain); } /** Exactly filtering method. \param pBuffIn - pointer to the buffer to apply filter. \param BuffInLength - length of the buffer to filter. */ template ReturnCode CFilterIIRStream::Process(T* pBuffIn, long BuffInLength) { // filter should be initialized before call of this method if(m_bIsInitialized == false) RET_ERROR; if(pBuffIn == NULL) return RET_INVALID_ARG; if(BuffInLength < 1) return RET_INVALID_ARG; ApplyFilter(pBuffIn, BuffInLength); return RET_OK; } /** Flushing. Used to free memory and set up all settings of the class to default */ template ReturnCode CFilterIIRStream::Flush() { m_bIsInitialized = false; m_pFilteredData = NULL; m_FilteredDataLength = 0; m_Latency = 0; m_a0 = 0; m_a1 = 0; m_a2 = 0; m_b0 = 0; m_b1 = 0; m_b2 = 0; m_x1 = 0; m_x2 = 0; m_y1 = 0; m_y2 = 0; m_ft = NONE; m_bIsBackProcessing = false; return RET_OK; } /** Internal filtering method. \param indata - pointer to the buffer to apply filter. \param numSampsToProcess - length of the buffer to filter. */ template void CFilterIIRStream::ApplyFilter(T* indata, long numSampsToProcess) { T out = 0, in = 0; for (int i = 0; i < numSampsToProcess; i++) { if(m_bIsBackProcessing == true) in = indata[numSampsToProcess - i - 1]; else in = indata[i]; // this optimization is usefull for some king of compilers/processors if(m_ft == LPF) { out = m_b0*(in + 2*m_x1 + m_x2) - m_a1*m_y1 - m_a2*m_y2; } else if(m_ft == HPF) { out = m_b0*(in - 2*m_x1 + m_x2) - m_a1*m_y1 - m_a2*m_y2; } else if(m_ft == BPF) { out = m_b0*(in - m_x2) - m_a1*m_y1 - m_a2*m_y2; } else if(m_ft == BSF) { out = (in + m_x2)/m_a0 + m_a1*(m_x1 - m_y1) - m_a2*m_y2; } else { // applying IIR routine out = (m_b0*in + m_b1*m_x1 + m_b2*m_x2 - m_a1*m_y1 - m_a2*m_y2); } m_x2 = m_x1; m_x1 = in; m_y2 = m_y1; m_y1 = out; if(m_bIsBackProcessing == true) indata[numSampsToProcess - i - 1] = out; else indata[i] = out; } m_pFilteredData = indata; m_FilteredDataLength = numSampsToProcess; m_Latency = 0; } /** Interface for the getting processed data. Returns pointer to the internal data of the class. */ template const T* CFilterIIRStream::GetFilteredData(void) { return m_pFilteredData; } /** Interface to get processed data length. Returns size of processed data in number of samples. */ template long CFilterIIRStream::GetFilteredDataLength(void) { return m_FilteredDataLength; } /** Interface to get latency of real-time processing. It means how many samples should we process to get first processed sample (or data block). It is associated to the delay in analog circuits. */ template int CFilterIIRStream::GetFilteredDataLatency(void) { return m_Latency; } // instantiation template class CFilterIIRStream; template class CFilterIIRStream; |
Итак, код это хорошо, но как же он работает? Как во всем этом разобраться? Наконец, как же работать с данным классом (или же шаблоном ежели Вам так понятней)? Законные вопросы и хоть я и сам себе их задал я постараюсь сформулировать адекватные ответы. Алгоритм работы с данным классом можно описать следующими этапами:
- Инициализация (Init(…));
- Блочная обработка данных (Process(…));
- Получение обработанных данных (методы GetFilteredData…(…));
- Сброс (Flush(…)).
Проанализируем по отдельности каждый из этапов.
Инициализация. Как можно заметить «невооруженным» глазом 😉 мы имеем несколько инициализаторов:
ReturnCode Init(
FILTERTYPE ft,
float f0,
float sr,
float Q,
float dBFilterGain);
ReturnCode Init(
FILTERTYPE ft,
float w0,
float bw = 0,
float dBFilterGain = 0);
Сделал ли я это только для того, чтобы запутать читателя?:) Конечно же нет! Это было сделано исключительно из за требований к гибкости будущей системы.
Разберемся с первым инициализатором. Первый параметр — тип фильтра. С этим ясно, ведь у нас имеется соответствующее перечисление FILTERTYPE. Второй параметр — определяющая частота, величина которой должна находится в пределах от 0 до частоты дискретизации деленной на 2. Третий параметр — собственно сама частота дискретизации (количество отчетов по одному каналу за одну секунду). Четвертый параметр — так называемая добротность фильтра (параметр довольно странный для человека, который не имеет отношения к электронике, но все же он значит отношение определяющей частоты к частотной полосе фильтра). Ну и последний параметр — это величина в децибелах на которую фильтр должен усилить или ослабить сигнал. Он используется только в случае, когда мы работаем с фильтрами PEAKING_EQ, LOW_SHELF и HIGH_SHELF.
Пришел черед разбора второго инициализатора. В отличии от первого варианта, который содержит 5 параметров на входе, второй имеет всего 4. По правде говоря меня чуть-чуть расстраивала работа с первым вариантом инициализатора и я решил сделать упрощенную его версию. Итак, первый параметр — тип фильтра, тот же самый что и в первом инициализаторе. Второй параметр — решающая частота, которая может принимать значения от 0 до Pi. Третий параметр — полоса фильтра значения которой должна иметь значения от 0 до Pi. Ну и последний параметр аналогичен параметру, описанному в предыдущем инициализаторе.
Обработка. Метод обработки у нас однин:
ReturnCode Process(T* pBuffIn, long BuffInLength);
Нужно помнить, что этот метод изменяет аргумент т.е. изменяет данные во входном блоке. Размер блока должен быть больше ноля.
Теперь о получении обработанных данных. Это можно сделать разными способами.
Первый способ это использовать методы-геттеры т.е. получить указатель на обработанные данные вызвав метод GetFilteredData() и получить размер обработанного буфера вызвав метод GetFilteredDataLength().
Второй способ банальный. Так как медод обработки данных изменяет данные, то мы можем просто передать буфер для обработки и из этого же буфера получить результат.
Сброс. Этот метод очень простой. Он всего-то возвращает переменные внутри экземпляра класса фильтра в «дифолтное» состояние.
ReturnCode Flush();