基于ProtoAudio的语音录制/静音检测

基于ProtoAudio的语音录制/静音检测

huiyi
2024-05-16 / 1 评论 / 485 阅读 / 正在检测是否收录...

以下代码全都开源在github: ai_client

概览

原定目标是创建一个智能录音模块,它能够自动检测静音并在没有实际音频输入时暂停录制,从而避免不必要的资源消耗。整个系统围绕Proto框架构建,包括了音频流管理、静音识别逻辑及数据处理等核心部分。最终成为AI助手的后端。

静音检测

原理

  • 能量分析: 通过计算每帧音频样本的能量总和,即其绝对值的平均,来评估当前的音量水平。
  • 阈值判定:设定一个能量阈值m_flitterSize,当连续几帧能量均值超过此阈值时,认为有声音,反之则视为静音。
  • 滤波处理:引入flitterFunc函数,基于能量序列判断是否连续超过静音阈值,考虑了容错率,防止因瞬间噪声触发录音。
int OnStreamCallBack(const void *inputBuffer,
                     void *outputBuffer,
                     unsigned long framesPerBuffer,
                     const PaStreamCallbackTimeInfo *timeInfo,
                     PaStreamCallbackFlags statusFlags,
                     void *sysData)

获取数据

// framesPerBuffer是本次采样数据地址,channelCount是通道数,双通道就是左右声道交错排列
auto fNum = framesPerBuffer * _this->m_params->channelCount;

均化振幅

// 最终的v也就是本次的振幅了
float total = 0;
float v = 0;
for (size_t i = 0; i < fNum; ++i) {
  auto tt = rprt[i];
  if (rprt[i] > middle) {
    v = static_cast<float>(rprt[i]) / static_cast<float>(maxPcm);
  } else {
    v = (static_cast<float>(fabs(rprt[i])) / static_cast<float>(fabs(minPcm)));
  }
  total += v;
}
v = total / static_cast<float>(fNum);

处理波动

// 本次均化之后的数据加入时间轮
_this->m_energyBuffer.push(v);

if (_this->m_energyBuffer.isFull()) {
    if (_this->m_records) {
      // 正在录音,滤波不通过取消录音
      if (!flitterFunc(_this->m_energyBuffer.getOrdered(),
                       _this->m_flitterSize,
                       _this->m_flitterFiltration)) {
        auto now = Timer::GetCurrentTimeMillis();
        if (now - _this->m_lastTime > _this->m_flitterTime) {
          bool r = true;
          if (_this->m_muteCallback) {
            r = _this->m_muteCallback(_this, _user);
          }
          _this->m_lastTime = now;
          // 防止爆闪
          _this->m_records = false;
          _this->m_insert = false;
        }
      }
    } else {
      // 没开始录音,超过阈值
      if (v > _this->m_flitterSize) {
        // 滤波,防止瞬时噪音
        if (flitterFunc(_this->m_energyBuffer.getOrdered(),
                        _this->m_flitterSize,
                        0.5)) {
          bool r = true;
          if (_this->m_StartCallback) {
            r = _this->m_StartCallback(_this, _user);
          }
          if (r) {
            _this->m_records = true;
            _this->m_insert = true;
          }
        }
      }else {
    // 因为过滤了短暂的爆音,但是这种过滤会导致丢掉说话开始时的一秒左右数据,所以要一个预存
        // 保留前reNum次采样的数据
        uint32_t reNum = 3;
        // 最大预存尺寸
        uint32_t maxIndex = reNum * fNum;
        if (_this->m_scanIndex >= maxIndex) {
          std::vector<short> tempV(maxIndex);
          tempV.clear();
          tempV.insert(tempV.end(), _this->m_pcmData.begin() + fNum, _this->m_pcmData.end());
          _this->m_scanIndex = tempV.size();
          _this->m_pcmData.clear();
          _this->m_pcmData.insert(_this->m_pcmData.end(), tempV.begin(), tempV.end());
          _this->m_insert = true;
        } else {
          _this->m_insert = true;
        }
      }
    }
  }

插入录制的数据

if (_this->m_insert || _this->m_records) {
    _this->m_pcmData.insert(_this->m_pcmData.end(), rprt, rprt + framesPerBuffer * _this->m_params->channelCount);
    if (_this->m_pcmData.size() >= 10000) {
      _this->m_scanIndex += static_cast<int32_t>(framesPerBuffer * _this->m_params->channelCount);
      if (_this->m_pcmData.size() >= _this->m_maxSize) {
        _this->m_scanIndex = 0;
        _this->m_pcmData.clear();
      }
    }
  }

使用

#include "audio/AudioClient.h"
#include "utils/extend/Logger.h"
#include <iostream>
#include <thread>

int main() {
  auto cli = AC::AudioClient::GetInstance();
  auto dev = AC::AudioClient::GetDefaultInputDevice();
  PrintDebug("default input device: {}", dev.Name.c_str());
  auto s = cli->OpenStream(dev);
  s.SetMuteCallback([](AC::AudioStream *stream, void *userData)->bool {
    stream->SaveData("re.wav", true);
    PrintInfo("saved");
    return true;
  });
  s.SetStartCallback([](AC::AudioStream *stream, void *userData)->bool {
    PrintInfo("start");
    return true;
  });
  std::thread a([&]() { s.Start(0.02,0.05,3000); });
  std::this_thread::sleep_for(std::chrono::seconds(5));

  while (true) {
    std::string cmd;
    std::cin >> cmd;
    if (cmd == "exit") {
      s.Stop();
      a.join();
      break;
    }

  }

  return 0;
}
0

评论 (1)

取消