Repository: csukuangfj/kaldi-native-fbank (language: python)

Issue:
如何同时支持mfcc特征
您好，请问如果增加mfcc特征支持，是将kaldi中feat的mfcc.cc，放进去可以吗？需要有很多代码修改嘛


Discussion / hints:
> 需要有很多代码修改嘛

不需要改很多。

> 是将kaldi中feat的mfcc.cc

代码可以照搬 kaldi, 需要一点点的修改。

Fix patch:
diff --git a/.github/workflows/linux-macos.yaml b/.github/workflows/linux-macos.yaml
index 8261ec2..acde044 100644
--- a/.github/workflows/linux-macos.yaml
+++ b/.github/workflows/linux-macos.yaml
@@ -29,7 +29,7 @@ jobs:
           fetch-depth: 0
 
       - name: Setup Python
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v4
         with:
           python-version: "3.8"
 
diff --git a/.github/workflows/windows-x64.yaml b/.github/workflows/windows-x64.yaml
index 9a6ef95..ba25626 100644
--- a/.github/workflows/windows-x64.yaml
+++ b/.github/workflows/windows-x64.yaml
@@ -51,7 +51,7 @@ jobs:
           ctest --verbose -C Release --output-on-failure -E py
 
       - name: Setup Python
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v4
         with:
           python-version: "3.8"
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 858b831..dbac3a0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,7 +25,7 @@ cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
 
 project(kaldi-native-fbank CXX C)
 
-set(KALDI_NATIVE_FBANK_VERSION "1.19.2")
+set(KALDI_NATIVE_FBANK_VERSION "1.19.3")
 
 # Disable warning about
 #
diff --git a/README.md b/README.md
index 2c874c1..c806ab6 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,10 @@ python3 -c "import kaldi_native_fbank; print(kaldi_native_fbank.__version__)"
 which should print the version you have installed.
 
 Please refer to
-<https://github.com/csukuangfj/kaldi-native-fbank/blob/master/kaldi-native-fbank/python/tests/test_online_fbank.py>
+
+  - <https://github.com/csukuangfj/kaldi-native-fbank/blob/master/kaldi-native-fbank/python/tests/test_online_fbank.py>
+  - <https://github.com/csukuangfj/kaldi-native-fbank/blob/master/kaldi-native-fbank/python/tests/test_online_mfcc.py>
+
 for usages.
 
 For easier reference, we post the above file below:
diff --git a/kaldi-native-fbank/csrc/CMakeLists.txt b/kaldi-native-fbank/csrc/CMakeLists.txt
index d07558a..69b7da4 100644
--- a/kaldi-native-fbank/csrc/CMakeLists.txt
+++ b/kaldi-native-fbank/csrc/CMakeLists.txt
@@ -3,6 +3,7 @@ include_directories(${PROJECT_SOURCE_DIR})
 set(sources
   feature-fbank.cc
   feature-functions.cc
+  feature-mfcc.cc
   feature-window.cc
   fftsg.cc
   kaldi-math.cc
diff --git a/kaldi-native-fbank/csrc/feature-fbank.cc b/kaldi-native-fbank/csrc/feature-fbank.cc
index 284cfb7..b73aa80 100644
--- a/kaldi-native-fbank/csrc/feature-fbank.cc
+++ b/kaldi-native-fbank/csrc/feature-fbank.cc
@@ -21,22 +21,16 @@
 #include "kaldi-native-fbank/csrc/feature-fbank.h"
 
 #include <algorithm>
-#include <cmath>
 #include <limits>
-#include <vector>
 #include <utility>
+#include <vector>
 
 #include "kaldi-native-fbank/csrc/feature-functions.h"
+#include "kaldi-native-fbank/csrc/kaldi-math.h"
 #include "kaldi-native-fbank/csrc/log.h"
 
 namespace knf {
 
-static void Sqrt(float *in_out, int32_t n) {
-  for (int32_t i = 0; i != n; ++i) {
-    in_out[i] = std::sqrt(in_out[i]);
-  }
-}
-
 std::ostream &operator<<(std::ostream &os, const FbankOptions &opts) {
   os << opts.ToString();
   return os;
diff --git a/kaldi-native-fbank/csrc/feature-mfcc.cc b/kaldi-native-fbank/csrc/feature-mfcc.cc
new file mode 100644
index 0000000..6b75285
--- /dev/null
+++ b/kaldi-native-fbank/csrc/feature-mfcc.cc
@@ -0,0 +1,174 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is copied/modified from kaldi/src/feat/feature-mfcc.cc
+//
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-functions.h"
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/kaldi-math.h"
+#include "kaldi-native-fbank/csrc/log.h"
+
+namespace knf {
+
+static std::vector<float> ComputeDctMatrix(int32_t num_rows, int32_t num_cols) {
+  // this function is copied from
+  // https://github.com/kaldi-asr/kaldi/blob/master/src/matrix/matrix-functions.cc#L592
+
+  std::vector<float> ans(num_rows * num_cols);
+  float *p = ans.data();
+
+  float normalizer = std::sqrt(1.0 / num_cols);  // normalizer for X_0
+
+  for (int32_t i = 0; i != num_cols; ++i) {
+    p[i] = normalizer;
+  }
+
+  normalizer = std::sqrt(2.0 / num_cols);  // normalizer for other elements
+
+  for (int32_t k = 1; k != num_rows; ++k) {
+    for (int32_t n = 0; n != num_cols; ++n) {
+      *(p + k * num_cols + n) =
+          normalizer *
+          std::cos(static_cast<double>(M_PI) / num_cols * (n + 0.5) * k);
+    }
+  }
+
+  return ans;
+}
+
+std::ostream &operator<<(std::ostream &os, const MfccOptions &opts) {
+  os << opts.ToString();
+  return os;
+}
+
+MfccComputer::MfccComputer(const MfccOptions &opts)
+    : opts_(opts),
+      rfft_(opts.frame_opts.PaddedWindowSize()),
+      mel_energies_(opts.mel_opts.num_bins) {
+  if (opts.energy_floor > 0.0f) {
+    log_energy_floor_ = logf(opts.energy_floor);
+  }
+
+  // We'll definitely need the filterbanks info for VTLN warping factor 1.0.
+  // [note: this call caches it.]
+  GetMelBanks(1.0f);
+
+  int32_t num_bins = opts.mel_opts.num_bins;
+
+  KNF_CHECK_LE(opts.num_ceps, num_bins)
+      << "num-ceps cannot be larger than num-mel-bins."
+      << " It should be smaller or equal. You provided num-ceps: "
+      << opts.num_ceps << "  and num-mel-bins: " << num_bins;
+
+  dct_matrix_ = ComputeDctMatrix(opts.num_ceps, num_bins);
+
+  if (opts.cepstral_lifter != 0.0) {
+    lifter_coeffs_ = std::vector<float>(opts.num_ceps);
+    // torch::empty({1, opts.num_ceps}, torch::kFloat32);
+    ComputeLifterCoeffs(opts.cepstral_lifter, &lifter_coeffs_);
+  }
+}
+
+MfccComputer::~MfccComputer() {
+  for (auto iter = mel_banks_.begin(); iter != mel_banks_.end(); ++iter)
+    delete iter->second;
+}
+
+const MelBanks *MfccComputer::GetMelBanks(float vtln_warp) {
+  MelBanks *this_mel_banks = nullptr;
+
+  // std::map<float, MelBanks *>::iterator iter = mel_banks_.find(vtln_warp);
+  auto iter = mel_banks_.find(vtln_warp);
+  if (iter == mel_banks_.end()) {
+    this_mel_banks = new MelBanks(opts_.mel_opts, opts_.frame_opts, vtln_warp);
+    mel_banks_[vtln_warp] = this_mel_banks;
+  } else {
+    this_mel_banks = iter->second;
+  }
+  return this_mel_banks;
+}
+
+void MfccComputer::Compute(float signal_raw_log_energy, float vtln_warp,
+                           std::vector<float> *signal_frame, float *feature) {
+  const MelBanks &mel_banks = *(GetMelBanks(vtln_warp));
+
+  KNF_CHECK_EQ(signal_frame->size(), opts_.frame_opts.PaddedWindowSize());
+
+  // Compute energy after window function (not the raw one).
+  if (opts_.use_energy && !opts_.raw_energy) {
+    signal_raw_log_energy = std::log(
+        std::max<float>(InnerProduct(signal_frame->data(), signal_frame->data(),
+                                     signal_frame->size()),
+                        std::numeric_limits<float>::epsilon()));
+  }
+  rfft_.Compute(signal_frame->data());  // signal_frame is modified in-place
+  ComputePowerSpectrum(signal_frame);
+
+  // Sum with mel filter banks over the power spectrum
+  mel_banks.Compute(signal_frame->data(), mel_energies_.data());
+
+  // Avoid log of zero (which should be prevented anyway by dithering).
+  for (int32_t i = 0; i != opts_.mel_opts.num_bins; ++i) {
+    auto t = std::max(mel_energies_[i], std::numeric_limits<float>::epsilon());
+    mel_energies_[i] = std::log(t);
+  }
+
+  // feature = dct_matrix_ * mel_energies [which now have log]
+  for (int32_t i = 0; i != opts_.num_ceps; ++i) {
+    feature[i] = InnerProduct(dct_matrix_.data() + i * opts_.mel_opts.num_bins,
+                              mel_energies_.data(), opts_.mel_opts.num_bins);
+  }
+
+  if (opts_.cepstral_lifter != 0.0) {
+    for (int32_t i = 0; i != opts_.num_ceps; ++i) {
+      feature[i] *= lifter_coeffs_[i];
+    }
+  }
+
+  if (opts_.use_energy) {
+    if (opts_.energy_floor > 0.0 && signal_raw_log_energy < log_energy_floor_) {
+      signal_raw_log_energy = log_energy_floor_;
+    }
+    feature[0] = signal_raw_log_energy;
+  }
+
+  if (opts_.htk_compat) {
+    float energy = feature[0];
+
+    for (int32_t i = 0; i < opts_.num_ceps - 1; ++i) {
+      feature[i] = feature[i + 1];
+    }
+
+    if (!opts_.use_energy) {
+      energy *= M_SQRT2;  // scale on C0 (actually removing a scale
+    }
+    // we previously added that's part of one common definition of
+    // the cosine transform.)
+    feature[opts_.num_ceps - 1] = energy;
+  }
+}
+
+}  // namespace knf
diff --git a/kaldi-native-fbank/csrc/feature-mfcc.h b/kaldi-native-fbank/csrc/feature-mfcc.h
new file mode 100644
index 0000000..32dc772
--- /dev/null
+++ b/kaldi-native-fbank/csrc/feature-mfcc.h
@@ -0,0 +1,151 @@
+/**
+ * Copyright 2009-2011  Karel Vesely;  Petr Motlicek;  Saarland University
+ *           2014-2016  Johns Hopkins University (author: Daniel Povey)
+ * Copyright 2024       Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is copied/modified from kaldi/src/feat/feature-mfcc.h
+
+#ifndef KALDI_NATIVE_FBANK_CSRC_FEATURE_MFCC_H_
+#define KALDI_NATIVE_FBANK_CSRC_FEATURE_MFCC_H_
+
+#include <cstdint>
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+#include "kaldi-native-fbank/csrc/rfft.h"
+
+namespace knf {
+
+/// MfccOptions contains basic options for computing MFCC features.
+// (this class is copied from kaldi)
+struct MfccOptions {
+  FrameExtractionOptions frame_opts;
+  MelBanksOptions mel_opts;
+
+  // Number of cepstra in MFCC computation (including C0)
+  int32_t num_ceps = 13;
+
+  // Use energy (not C0) in MFCC computation
+  bool use_energy = true;
+
+  // Floor on energy (absolute, not relative) in MFCC
+  // computation. Only makes a difference if use_energy=true;
+  // only necessary if dither=0.0.
+  // Suggested values: 0.1 or 1.0
+  float energy_floor = 0.0;
+
+  // If true, compute energy before preemphasis and windowing
+  bool raw_energy = true;
+
+  // Constant that controls scaling of MFCCs
+  float cepstral_lifter = 22.0;
+
+  // If true, put energy or C0 last and use a factor of
+  // sqrt(2) on C0.
+  // Warning: not sufficient to get HTK compatible features
+  // (need to change other parameters)
+  bool htk_compat = false;
+
+  MfccOptions() { mel_opts.num_bins = 23; }
+
+  std::string ToString() const {
+    std::ostringstream os;
+    os << "MfccOptions(";
+    os << "frame_opts=" << frame_opts.ToString() << ", ";
+    os << "mel_opts=" << mel_opts.ToString() << ", ";
+
+    os << "num_ceps=" << num_ceps << ", ";
+    os << "use_energy=" << (use_energy ? "True" : "False") << ", ";
+    os << "energy_floor=" << energy_floor << ", ";
+    os << "raw_energy=" << (raw_energy ? "True" : "False") << ", ";
+    os << "cepstral_lifter=" << cepstral_lifter << ", ";
+    os << "htk_compat=" << (htk_compat ? "True" : "False") << ")";
+
+    return os.str();
+  }
+};
+
+std::ostream &operator<<(std::ostream &os, const MfccOptions &opts);
+
+class MfccComputer {
+ public:
+  using Options = MfccOptions;
+
+  explicit MfccComputer(const MfccOptions &opts);
+  ~MfccComputer();
+
+  int32_t Dim() const { return opts_.num_ceps; }
+
+  // if true, compute log_energy_pre_window but after dithering and dc removal
+  bool NeedRawLogEnergy() const { return opts_.use_energy && opts_.raw_energy; }
+
+  const FrameExtractionOptions &GetFrameOptions() const {
+    return opts_.frame_opts;
+  }
+
+  const MfccOptions &GetOptions() const { return opts_; }
+
+  /**
+     Function that computes one frame of features from
+     one frame of signal.
+
+     @param [in] signal_raw_log_energy The log-energy of the frame of the signal
+         prior to windowing and pre-emphasis, or
+         log(numeric_limits<float>::min()), whichever is greater.  Must be
+         ignored by this function if this class returns false from
+         this->NeedsRawLogEnergy().
+     @param [in] vtln_warp  The VTLN warping factor that the user wants
+         to be applied when computing features for this utterance.  Will
+         normally be 1.0, meaning no warping is to be done.  The value will
+         be ignored for feature types that don't support VLTN, such as
+         spectrogram features.
+     @param [in] signal_frame  One frame of the signal,
+       as extracted using the function ExtractWindow() using the options
+       returned by this->GetFrameOptions().  The function will use the
+       vector as a workspace, which is why it's a non-const pointer.
+     @param [out] feature  Pointer to a vector of size this->Dim(), to which
+         the computed feature will be written. It should be pre-allocated.
+  */
+  void Compute(float signal_raw_log_energy, float vtln_warp,
+               std::vector<float> *signal_frame, float *feature);
+
+ private:
+  const MelBanks *GetMelBanks(float vtln_warp);
+
+  MfccOptions opts_;
+  float log_energy_floor_;
+  std::map<float, MelBanks *> mel_banks_;  // float is VTLN coefficient.
+  Rfft rfft_;
+
+  // temp buffer of size num_mel_bins = opts.mel_opts.num_bins
+  std::vector<float> mel_energies_;
+
+  // opts_.num_ceps
+  std::vector<float> lifter_coeffs_;
+
+  // [num_ceps][num_mel_bins]
+  std::vector<float> dct_matrix_;
+};
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_CSRC_FEATURE_MFCC_H_
diff --git a/kaldi-native-fbank/csrc/kaldi-math.cc b/kaldi-native-fbank/csrc/kaldi-math.cc
index 4b966a1..bcc0dcc 100644
--- a/kaldi-native-fbank/csrc/kaldi-math.cc
+++ b/kaldi-native-fbank/csrc/kaldi-math.cc
@@ -10,6 +10,8 @@
 #include <mutex>  // NOLINT
 #endif
 
+#include <cmath>
+
 namespace knf {
 
 int Rand(struct RandomState *state) {
@@ -40,4 +42,10 @@ RandomState::RandomState() {
   seed = unsigned(Rand()) + 27437;
 }
 
+void Sqrt(float *in_out, int32_t n) {
+  for (int32_t i = 0; i != n; ++i) {
+    in_out[i] = std::sqrt(in_out[i]);
+  }
+}
+
 }  // namespace knf
diff --git a/kaldi-native-fbank/csrc/kaldi-math.h b/kaldi-native-fbank/csrc/kaldi-math.h
index a0e08d2..4db3f83 100644
--- a/kaldi-native-fbank/csrc/kaldi-math.h
+++ b/kaldi-native-fbank/csrc/kaldi-math.h
@@ -7,7 +7,8 @@
 #ifndef KALDI_NATIVE_FBANK_CSRC_KALDI_MATH_H_
 #define KALDI_NATIVE_FBANK_CSRC_KALDI_MATH_H_
 
-#include <cmath>    // logf, sqrtf, cosf
+#include <cmath>  // logf, sqrtf, cosf
+#include <cstdint>
 #include <cstdlib>  // RAND_MAX
 
 #ifndef M_PI
@@ -18,6 +19,10 @@
 #define M_2PI 6.283185307179586476925286766559005
 #endif
 
+#ifndef M_SQRT2
+#define M_SQRT2 1.4142135623730950488016887
+#endif
+
 namespace knf {
 
 inline float Log(float x) { return logf(x); }
@@ -41,5 +46,7 @@ inline float RandGauss(struct RandomState *state = NULL) {
                             cosf(2 * M_PI * RandUniform(state)));
 }
 
+void Sqrt(float *in_out, int32_t n);
+
 }  // namespace knf
 #endif  // KALDI_NATIVE_FBANK_CSRC_KALDI_MATH_H_
diff --git a/kaldi-native-fbank/csrc/mel-computations.cc b/kaldi-native-fbank/csrc/mel-computations.cc
index af4c9f1..c083a56 100644
--- a/kaldi-native-fbank/csrc/mel-computations.cc
+++ b/kaldi-native-fbank/csrc/mel-computations.cc
@@ -21,11 +21,13 @@
 #include "kaldi-native-fbank/csrc/mel-computations.h"
 
 #include <stdio.h>
+
 #include <algorithm>
 #include <sstream>
 #include <vector>
 
 #include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/kaldi-math.h"
 #include "kaldi-native-fbank/csrc/log.h"
 
 namespace knf {
@@ -408,4 +410,13 @@ void MelBanks::Compute(const float *power_spectrum,
   }
 }
 
+void ComputeLifterCoeffs(float Q, std::vector<float> *coeffs) {
+  // Compute liftering coefficients (scaling on cepstral coeffs)
+  // coeffs are numbered slightly differently from HTK: the zeroth
+  // index is C0, which is not affected.
+  for (int32_t i = 0; i != static_cast<int32_t>(coeffs->size()); ++i) {
+    (*coeffs)[i] = 1.0 + 0.5 * Q * sin(M_PI * i / Q);
+  }
+}
+
 }  // namespace knf
diff --git a/kaldi-native-fbank/csrc/mel-computations.h b/kaldi-native-fbank/csrc/mel-computations.h
index 4c229e6..c81ce97 100644
--- a/kaldi-native-fbank/csrc/mel-computations.h
+++ b/kaldi-native-fbank/csrc/mel-computations.h
@@ -20,11 +20,11 @@
 #define KALDI_NATIVE_FBANK_CSRC_MEL_COMPUTATIONS_H_
 
 #include <cmath>
+#include <cstdint>
+#include <sstream>
 #include <string>
 #include <utility>
 #include <vector>
-#include <cstdint>
-#include <sstream>
 
 #include "kaldi-native-fbank/csrc/feature-window.h"
 
@@ -172,6 +172,11 @@ class MelBanks {
   bool htk_mode_ = false;
 };
 
+// Compute liftering coefficients (scaling on cepstral coeffs)
+// coeffs are numbered slightly differently from HTK: the zeroth
+// index is C0, which is not affected.
+void ComputeLifterCoeffs(float Q, std::vector<float> *coeffs);
+
 }  // namespace knf
 
 #endif  // KALDI_NATIVE_FBANK_CSRC_MEL_COMPUTATIONS_H_
diff --git a/kaldi-native-fbank/csrc/online-feature.cc b/kaldi-native-fbank/csrc/online-feature.cc
index 57737a3..13c4369 100644
--- a/kaldi-native-fbank/csrc/online-feature.cc
+++ b/kaldi-native-fbank/csrc/online-feature.cc
@@ -22,9 +22,9 @@
 #include "kaldi-native-fbank/csrc/online-feature.h"
 
 #include <algorithm>
+#include <cstddef>
 #include <utility>
 #include <vector>
-#include <cstddef>
 
 #include "kaldi-native-fbank/csrc/feature-window.h"
 #include "kaldi-native-fbank/csrc/log.h"
@@ -163,6 +163,7 @@ void OnlineGenericBaseFeature<C>::ComputeFeatures() {
 }
 
 template class OnlineGenericBaseFeature<FbankComputer>;
+template class OnlineGenericBaseFeature<MfccComputer>;
 template class OnlineGenericBaseFeature<WhisperFeatureComputer>;
 
 }  // namespace knf
diff --git a/kaldi-native-fbank/csrc/online-feature.h b/kaldi-native-fbank/csrc/online-feature.h
index d9fea75..3d42fd4 100644
--- a/kaldi-native-fbank/csrc/online-feature.h
+++ b/kaldi-native-fbank/csrc/online-feature.h
@@ -26,8 +26,9 @@
 #include <vector>
 
 #include "kaldi-native-fbank/csrc/feature-fbank.h"
-#include "kaldi-native-fbank/csrc/whisper-feature.h"
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
 #include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/whisper-feature.h"
 
 namespace knf {
 
@@ -144,6 +145,7 @@ class OnlineGenericBaseFeature {
 };
 
 using OnlineFbank = OnlineGenericBaseFeature<FbankComputer>;
+using OnlineMfcc = OnlineGenericBaseFeature<MfccComputer>;
 using OnlineWhisperFbank = OnlineGenericBaseFeature<WhisperFeatureComputer>;
 
 }  // namespace knf
diff --git a/kaldi-native-fbank/python/csrc/CMakeLists.txt b/kaldi-native-fbank/python/csrc/CMakeLists.txt
index e0ffa3b..0366b08 100644
--- a/kaldi-native-fbank/python/csrc/CMakeLists.txt
+++ b/kaldi-native-fbank/python/csrc/CMakeLists.txt
@@ -1,5 +1,6 @@
 pybind11_add_module(_kaldi_native_fbank
   feature-fbank.cc
+  feature-mfcc.cc
   feature-window.cc
   kaldi-native-fbank.cc
   mel-computations.cc
@@ -15,7 +16,11 @@ if(APPLE)
     OUTPUT_VARIABLE PYTHON_SITE_PACKAGE_DIR
   )
   message(STATUS "PYTHON_SITE_PACKAGE_DIR: ${PYTHON_SITE_PACKAGE_DIR}")
-  target_link_libraries(_kaldi_native_fbank PRIVATE "-Wl,-rpath,${PYTHON_SITE_PACKAGE_DIR}")
+  if(PYTHON_SITE_PACKAGE_DIR STREQUAL "")
+    message(WARNING "PYTHON_SITE_PACKAGE_DIR is empty!")
+  else()
+    target_link_libraries(_kaldi_native_fbank PRIVATE "-Wl,-rpath,${PYTHON_SITE_PACKAGE_DIR}")
+  endif()
 endif()
 
 if(NOT WIN32)
diff --git a/kaldi-native-fbank/python/csrc/feature-mfcc.cc b/kaldi-native-fbank/python/csrc/feature-mfcc.cc
new file mode 100644
index 0000000..d910016
--- /dev/null
+++ b/kaldi-native-fbank/python/csrc/feature-mfcc.cc
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c)  2024  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
+
+#include <string>
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+#include "kaldi-native-fbank/python/csrc/feature-mfcc.h"
+#include "kaldi-native-fbank/python/csrc/utils.h"
+
+namespace knf {
+
+static void PybindMfccOptions(py::module &m) {  // NOLINT
+  using PyClass = MfccOptions;
+  py::class_<PyClass>(m, "MfccOptions")
+      .def(py::init<>())
+      .def_readwrite("frame_opts", &PyClass::frame_opts)
+      .def_readwrite("mel_opts", &PyClass::mel_opts)
+      .def_readwrite("num_ceps", &PyClass::num_ceps)
+      .def_readwrite("use_energy", &PyClass::use_energy)
+      .def_readwrite("energy_floor", &PyClass::energy_floor)
+      .def_readwrite("raw_energy", &PyClass::raw_energy)
+      .def_readwrite("cepstral_lifter", &PyClass::cepstral_lifter)
+      .def_readwrite("htk_compat", &PyClass::htk_compat)
+      .def("__str__",
+           [](const PyClass &self) -> std::string { return self.ToString(); })
+      .def("as_dict",
+           [](const PyClass &self) -> py::dict { return AsDict(self); })
+      .def_static(
+          "from_dict",
+          [](py::dict dict) -> PyClass { return MfccOptionsFromDict(dict); })
+      .def(py::pickle(
+          [](const PyClass &self) -> py::dict { return AsDict(self); },
+          [](py::dict dict) -> PyClass { return MfccOptionsFromDict(dict); }));
+}
+
+void PybindFeatureMfcc(py::module &m) {  // NOLINT
+  PybindMfccOptions(m);
+}
+
+}  // namespace knf
diff --git a/kaldi-native-fbank/python/csrc/feature-mfcc.h b/kaldi-native-fbank/python/csrc/feature-mfcc.h
new file mode 100644
index 0000000..1a44578
--- /dev/null
+++ b/kaldi-native-fbank/python/csrc/feature-mfcc.h
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c)  2024  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_MFCC_H_
+#define KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_MFCC_H_
+
+#include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
+
+namespace knf {
+
+void PybindFeatureMfcc(py::module &m);  // NOLINT
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_MFCC_H_
diff --git a/kaldi-native-fbank/python/csrc/kaldi-native-fbank.cc b/kaldi-native-fbank/python/csrc/kaldi-native-fbank.cc
index 477cd2b..be12a47 100644
--- a/kaldi-native-fbank/python/csrc/kaldi-native-fbank.cc
+++ b/kaldi-native-fbank/python/csrc/kaldi-native-fbank.cc
@@ -19,6 +19,7 @@
 #include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
 
 #include "kaldi-native-fbank/python/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/python/csrc/feature-mfcc.h"
 #include "kaldi-native-fbank/python/csrc/feature-window.h"
 #include "kaldi-native-fbank/python/csrc/mel-computations.h"
 #include "kaldi-native-fbank/python/csrc/online-feature.h"
@@ -31,6 +32,7 @@ PYBIND11_MODULE(_kaldi_native_fbank, m) {
   PybindFeatureWindow(m);
   PybindMelComputations(m);
   PybindFeatureFbank(m);
+  PybindFeatureMfcc(m);
   PybindRfft(m);
 
   PybindOnlineFeature(m);
diff --git a/kaldi-native-fbank/python/csrc/online-feature.cc b/kaldi-native-fbank/python/csrc/online-feature.cc
index aebf33b..d0bb0ec 100644
--- a/kaldi-native-fbank/python/csrc/online-feature.cc
+++ b/kaldi-native-fbank/python/csrc/online-feature.cc
@@ -18,12 +18,13 @@
 
 #include "kaldi-native-fbank/python/csrc/online-feature.h"
 
+#include <cstdint>
 #include <string>
 #include <vector>
-#include <cstdint>
 
-#include "kaldi-native-fbank/csrc/online-feature.h"
 #include "kaldi-native-fbank/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
+#include "kaldi-native-fbank/csrc/online-feature.h"
 #include "kaldi-native-fbank/csrc/whisper-feature.h"
 
 namespace pybind11 {
@@ -66,11 +67,15 @@ void PybindOnlineFeatureTpl(py::module &m,  // NOLINT
           },
           py::arg("sampling_rate"), py::arg("waveform"),
           py::call_guard<py::gil_scoped_release>())
-      .def("input_finished", &PyClass::InputFinished);
+      .def("input_finished", &PyClass::InputFinished,
+           py::call_guard<py::gil_scoped_release>())
+      .def("pop", &PyClass::Pop, py::arg("n"),
+           py::call_guard<py::gil_scoped_release>());
 }
 
 void PybindOnlineFeature(py::module &m) {  // NOLINT
   PybindOnlineFeatureTpl<FbankComputer>(m, "OnlineFbank");
+  PybindOnlineFeatureTpl<MfccComputer>(m, "OnlineMfcc");
   PybindOnlineFeatureTpl<WhisperFeatureComputer>(m, "OnlineWhisperFbank");
 }
 
diff --git a/kaldi-native-fbank/python/csrc/utils.cc b/kaldi-native-fbank/python/csrc/utils.cc
index 7e6b045..d7e259b 100644
--- a/kaldi-native-fbank/python/csrc/utils.cc
+++ b/kaldi-native-fbank/python/csrc/utils.cc
@@ -20,6 +20,8 @@
 
 #include <string>
 
+#include "kaldi-native-fbank/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
 #include "kaldi-native-fbank/csrc/feature-window.h"
 
 #define FROM_DICT(type, key)         \
@@ -132,6 +134,42 @@ py::dict AsDict(const FbankOptions &opts) {
   return dict;
 }
 
+MfccOptions MfccOptionsFromDict(py::dict dict) {
+  MfccOptions opts;
+
+  if (dict.contains("frame_opts")) {
+    opts.frame_opts = FrameExtractionOptionsFromDict(dict["frame_opts"]);
+  }
+
+  if (dict.contains("mel_opts")) {
+    opts.mel_opts = MelBanksOptionsFromDict(dict["mel_opts"]);
+  }
+
+  FROM_DICT(int_, num_ceps);
+  FROM_DICT(bool_, use_energy);
+  FROM_DICT(float_, energy_floor);
+  FROM_DICT(bool_, raw_energy);
+  FROM_DICT(float_, cepstral_lifter);
+  FROM_DICT(bool_, htk_compat);
+
+  return opts;
+}
+
+py::dict AsDict(const MfccOptions &opts) {
+  py::dict dict;
+
+  dict["frame_opts"] = AsDict(opts.frame_opts);
+  dict["mel_opts"] = AsDict(opts.mel_opts);
+  AS_DICT(num_ceps);
+  AS_DICT(use_energy);
+  AS_DICT(energy_floor);
+  AS_DICT(raw_energy);
+  AS_DICT(cepstral_lifter);
+  AS_DICT(htk_compat);
+
+  return dict;
+}
+
 #undef FROM_DICT
 #undef AS_DICT
 
diff --git a/kaldi-native-fbank/python/csrc/utils.h b/kaldi-native-fbank/python/csrc/utils.h
index bb78165..c273c85 100644
--- a/kaldi-native-fbank/python/csrc/utils.h
+++ b/kaldi-native-fbank/python/csrc/utils.h
@@ -20,6 +20,7 @@
 #define KALDI_NATIVE_FBANK_PYTHON_CSRC_UTILS_H_
 
 #include "kaldi-native-fbank/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
 #include "kaldi-native-fbank/csrc/feature-window.h"
 #include "kaldi-native-fbank/csrc/mel-computations.h"
 #include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
@@ -47,6 +48,9 @@ py::dict AsDict(const MelBanksOptions &opts);
 FbankOptions FbankOptionsFromDict(py::dict dict);
 py::dict AsDict(const FbankOptions &opts);
 
+MfccOptions MfccOptionsFromDict(py::dict dict);
+py::dict AsDict(const MfccOptions &opts);
+
 }  // namespace knf
 
 #endif  // KALDI_NATIVE_FBANK_PYTHON_CSRC_UTILS_H_
diff --git a/kaldi-native-fbank/python/kaldi_native_fbank/__init__.py b/kaldi-native-fbank/python/kaldi_native_fbank/__init__.py
index f444235..d0b92f0 100644
--- a/kaldi-native-fbank/python/kaldi_native_fbank/__init__.py
+++ b/kaldi-native-fbank/python/kaldi_native_fbank/__init__.py
@@ -3,7 +3,9 @@
     FeatureWindowFunction,
     FrameExtractionOptions,
     MelBanksOptions,
+    MfccOptions,
     OnlineFbank,
+    OnlineMfcc,
     OnlineWhisperFbank,
     Rfft,
 )


Test patch:
diff --git a/.github/workflows/test-wheel.yaml b/.github/workflows/test-wheel.yaml
index d957494..f16be22 100644
--- a/.github/workflows/test-wheel.yaml
+++ b/.github/workflows/test-wheel.yaml
@@ -32,7 +32,7 @@ jobs:
       fail-fast: false
       matrix:
         os: [windows-latest, macos-latest, ubuntu-latest]
-        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
 
     steps:
       - uses: actions/checkout@v2
@@ -40,7 +40,7 @@ jobs:
           fetch-depth: 0
 
       - name: Setup Python
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
diff --git a/kaldi-native-fbank/python/tests/CMakeLists.txt b/kaldi-native-fbank/python/tests/CMakeLists.txt
index 2b791a8..d2c671c 100644
--- a/kaldi-native-fbank/python/tests/CMakeLists.txt
+++ b/kaldi-native-fbank/python/tests/CMakeLists.txt
@@ -24,6 +24,8 @@ set(py_test_files
   test_frame_extraction_options.py
   test_mel_bank_options.py
   test_online_whisper_fbank.py
+  test_online_fbank.py
+  test_online_mfcc.py
   # test_rfft.py
 )
 
diff --git a/kaldi-native-fbank/python/tests/test_online_mfcc.py b/kaldi-native-fbank/python/tests/test_online_mfcc.py
new file mode 100755
index 0000000..794da95
--- /dev/null
+++ b/kaldi-native-fbank/python/tests/test_online_mfcc.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+import sys
+
+try:
+    import kaldifeat
+except:
+    print("Please install kaldifeat first")
+    sys.exit(0)
+
+import kaldi_native_fbank as knf
+import torch
+
+
+def main():
+    sampling_rate = 16000
+    samples = torch.randn(sampling_rate * 10)
+
+    opts = kaldifeat.MfccOptions()
+    opts.frame_opts.dither = 0
+    opts.num_ceps = 40
+    opts.mel_opts.num_bins = 40
+    opts.mel_opts.high_freq = -200
+    opts.frame_opts.snip_edges = False
+    online_mfcc = kaldifeat.OnlineMfcc(opts)
+
+    online_mfcc.accept_waveform(sampling_rate, samples)
+
+    opts = knf.MfccOptions()
+    opts.frame_opts.dither = 0
+    opts.num_ceps = 40
+    opts.mel_opts.num_bins = 40
+    opts.mel_opts.high_freq = -200
+    opts.frame_opts.snip_edges = False
+
+    mfcc = knf.OnlineMfcc(opts)
+    mfcc.accept_waveform(sampling_rate, samples.tolist())
+
+    assert online_mfcc.num_frames_ready == mfcc.num_frames_ready
+    for i in range(mfcc.num_frames_ready):
+        f1 = online_mfcc.get_frame(i)
+        f2 = torch.from_numpy(mfcc.get_frame(i))
+        assert torch.allclose(f1, f2, atol=1e-3), (i, (f1 - f2).abs().max(), f1, f2)
+
+
+if __name__ == "__main__":
+    torch.manual_seed(20240603)
+    main()
+    print("success")