Loading... 以下代码全都开源在github: [ai_client](https://github.com/Huiyicc/ai_client) # 概览 原定目标是创建一个智能录音模块,它能够自动检测静音并在没有实际音频输入时暂停录制,从而避免不必要的资源消耗。整个系统围绕Proto框架构建,包括了音频流管理、静音识别逻辑及数据处理等核心部分。最终成为AI助手的后端。 # 静音检测 ### 原理 - **能量分析**: 通过计算每帧音频样本的能量总和,即其绝对值的平均,来评估当前的音量水平。 * **阈值判定**:设定一个能量阈值`m_flitterSize`,当连续几帧能量均值超过此阈值时,认为有声音,反之则视为静音。 * **滤波处理**:引入`flitterFunc`函数,基于能量序列判断是否连续超过静音阈值,考虑了容错率,防止因瞬间噪声触发录音。 ```cpp int OnStreamCallBack(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *sysData) ``` ### 获取数据 ```cpp // framesPerBuffer是本次采样数据地址,channelCount是通道数,双通道就是左右声道交错排列 auto fNum = framesPerBuffer * _this->m_params->channelCount; ``` ### 均化振幅 ```cpp // 最终的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); ``` ### 处理波动 ```cpp // 本次均化之后的数据加入时间轮 _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(); } } } ``` ### 使用 ```cpp #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; } ``` 最后修改:2024 年 05 月 16 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏