小智AI语音测试流程
梳理小智AI语音处理流程
·
AS_EVENT_AUDIO_TESTING_RUNNING从按键触发到录音到播放的所有代码流程:
AS_EVENT_AUDIO_TESTING_RUNNING 完整代码流程梳理
1. 按键触发阶段
触发路径:
GPIO0 (BOOT按钮) → Button::OnClick() → Application::ToggleChatState()
代码位置:
main/boards/esp-box-3/esp_box3_board.cc第85-92行:
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
状态判断逻辑:
main/application.cc第179-188行:
void Application::ToggleChatState() {
// ... 其他状态处理 ...
else if (device_state_ == kDeviceStateWifiConfiguring) {
audio_service_.EnableAudioTesting(true); // 启用音频测试
SetDeviceState(kDeviceStateAudioTesting);
return;
} else if (device_state_ == kDeviceStateAudioTesting) {
audio_service_.EnableAudioTesting(false); // 停止音频测试
SetDeviceState(kDeviceStateWifiConfiguring);
return;
}
}
2. 音频测试启用阶段
AudioService::EnableAudioTesting(true):
main/audio/audio_service.cc第542-548行:
void AudioService::EnableAudioTesting(bool enable) {
ESP_LOGI(TAG, "%s audio testing", enable ? "Enabling" : "Disabling");
if (enable) {
xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING);
} else {
xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING);
// 将测试队列移动到解码队列进行播放
std::lock_guard<std::mutex> lock(audio_queue_mutex_);
audio_decode_queue_ = std::move(audio_testing_queue_);
audio_queue_cv_.notify_all();
}
}
3. 录音阶段
AudioInputTask 录音循环:
main/audio/audio_service.cc第124-145行:
void AudioService::AudioInputTask() {
while (true) {
EventBits_t bits = xEventGroupWaitBits(event_group_,
AS_EVENT_AUDIO_TESTING_RUNNING | /* 其他事件 */,
pdFALSE, pdFALSE, portMAX_DELAY);
// 音频测试录音处理
if (bits & AS_EVENT_AUDIO_TESTING_RUNNING) {
// 检查队列是否已满(最大10秒录音)
if (audio_testing_queue_.size() >= AUDIO_TESTING_MAX_DURATION_MS / OPUS_FRAME_DURATION_MS) {
ESP_LOGW(TAG, "Audio testing queue is full, stopping audio testing");
EnableAudioTesting(false);
continue;
}
std::vector<int16_t> data;
int samples = OPUS_FRAME_DURATION_MS * 16000 / 1000; // 60ms * 16kHz = 960 samples
if (ReadAudioData(data, 16000, samples)) {
// 双声道转单声道(提取左声道)
if (codec_->input_channels() == 2) {
auto mono_data = std::vector<int16_t>(data.size() / 2);
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
mono_data[i] = data[j];
}
data = std::move(mono_data);
}
// 推送到编码队列
PushTaskToEncodeQueue(kAudioTaskTypeEncodeToTestingQueue, std::move(data));
continue;
}
}
}
}
ReadAudioData 音频数据读取:
main/audio/audio_service.cc第89-122行:
bool AudioService::ReadAudioData(std::vector<int16_t>& data, int sample_rate, int samples) {
// 启用音频输入
if (!codec_->input_enabled()) {
codec_->EnableInput(true);
}
// 处理采样率转换和双声道数据
if (codec_->input_sample_rate() != sample_rate) {
// 重采样逻辑...
} else {
data.resize(samples * codec_->input_channels());
if (!codec_->InputData(data)) {
return false;
}
}
// 更新统计信息
last_input_time_ = std::chrono::steady_clock::now();
debug_statistics_.input_count++;
return true;
}
4. 编码阶段
PushTaskToEncodeQueue 推送编码任务:
main/audio/audio_service.cc第350-368行:
void AudioService::PushTaskToEncodeQueue(AudioTaskType type, std::vector<int16_t>&& pcm) {
auto task = std::make_unique<AudioTask>();
task->type = type; // kAudioTaskTypeEncodeToTestingQueue
task->pcm = std::move(pcm);
std::unique_lock<std::mutex> lock(audio_queue_mutex_);
// 等待编码队列有空间
audio_queue_cv_.wait(lock, [this]() {
return audio_encode_queue_.size() < MAX_ENCODE_TASKS_IN_QUEUE;
});
audio_encode_queue_.push_back(std::move(task));
audio_queue_cv_.notify_all();
}
OpusCodecTask Opus编码处理:
main/audio/audio_service.cc第280-325行:
void AudioService::OpusCodecTask() {
while (true) {
std::unique_lock<std::mutex> lock(audio_queue_mutex_);
audio_queue_cv_.wait(lock, [this]() {
return service_stopped_ ||
(!audio_encode_queue_.empty() && /* 其他条件 */);
});
// 编码音频数据
if (!audio_encode_queue_.empty()) {
auto task = std::move(audio_encode_queue_.front());
audio_encode_queue_.pop_front();
lock.unlock();
auto packet = std::make_unique<AudioStreamPacket>();
packet->frame_duration = OPUS_FRAME_DURATION_MS; // 60ms
packet->sample_rate = 16000;
packet->timestamp = task->timestamp;
// Opus编码
if (!opus_encoder_->Encode(std::move(task->pcm), packet->payload)) {
ESP_LOGE(TAG, "Failed to encode audio");
continue;
}
// 根据任务类型存储到不同队列
if (task->type == kAudioTaskTypeEncodeToTestingQueue) {
std::lock_guard<std::mutex> lock(audio_queue_mutex_);
audio_testing_queue_.push_back(std::move(packet)); // 存储到测试队列
}
debug_statistics_.encode_count++;
}
}
}
5. 播放触发阶段
再次按键停止录音并触发播放:
- 当再次按下BOOT按钮时,调用
EnableAudioTesting(false) main/audio/audio_service.cc第548-552行:
} else {
xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING);
// 关键:将测试队列移动到解码队列进行播放
std::lock_guard<std::mutex> lock(audio_queue_mutex_);
audio_decode_queue_ = std::move(audio_testing_queue_);
audio_queue_cv_.notify_all();
}
6. 解码阶段
OpusCodecTask Opus解码处理:
main/audio/audio_service.cc第245-279行:
// 解码音频数据
if (!audio_decode_queue_.empty() && audio_playback_queue_.size() < MAX_PLAYBACK_TASKS_IN_QUEUE) {
auto packet = std::move(audio_decode_queue_.front());
audio_decode_queue_.pop_front();
lock.unlock();
auto task = std::make_unique<AudioTask>();
task->type = kAudioTaskTypeDecodeToPlaybackQueue;
task->timestamp = packet->timestamp;
// 设置解码参数
SetDecodeSampleRate(packet->sample_rate, packet->frame_duration);
// Opus解码
if (opus_decoder_->Decode(std::move(packet->payload), task->pcm)) {
// 重采样到输出采样率
if (opus_decoder_->sample_rate() != codec_->output_sample_rate()) {
int target_size = output_resampler_.GetOutputSamples(task->pcm.size());
std::vector<int16_t> resampled(target_size);
output_resampler_.Process(task->pcm.data(), task->pcm.size(), resampled.data());
task->pcm = std::move(resampled);
}
// 推送到播放队列
lock.lock();
audio_playback_queue_.push_back(std::move(task));
audio_queue_cv_.notify_all();
}
debug_statistics_.decode_count++;
}
7. 播放阶段
AudioOutputTask 音频播放:
main/audio/audio_service.cc第200-225行:
void AudioService::AudioOutputTask() {
while (true) {
std::unique_lock<std::mutex> lock(audio_queue_mutex_);
// 等待播放队列有数据
audio_queue_cv_.wait(lock, [this]() {
return !audio_playback_queue_.empty() || service_stopped_;
});
if (service_stopped_) break;
auto task = std::move(audio_playback_queue_.front());
audio_playback_queue_.pop_front();
lock.unlock();
// 启用音频输出
if (!codec_->output_enabled()) {
codec_->EnableOutput(true);
}
// 播放PCM数据
codec_->OutputData(task->pcm);
// 更新统计信息
last_output_time_ = std::chrono::steady_clock::now();
debug_statistics_.playback_count++;
}
}
完整数据流程图
[GPIO0按键]
↓
[Button::OnClick()]
↓
[Application::ToggleChatState()]
↓
[AudioService::EnableAudioTesting(true)]
↓
[设置AS_EVENT_AUDIO_TESTING_RUNNING事件位]
↓
[AudioInputTask检测到事件]
↓
[ReadAudioData() - 从麦克风读取PCM数据]
↓
[双声道→单声道转换]
↓
[PushTaskToEncodeQueue(kAudioTaskTypeEncodeToTestingQueue)]
↓
[OpusCodecTask - Opus编码]
↓
[存储到audio_testing_queue_]
↓
[再次按键 - EnableAudioTesting(false)]
↓
[audio_testing_queue_ → audio_decode_queue_]
↓
[OpusCodecTask - Opus解码]
↓
[重采样到输出采样率]
↓
[推送到audio_playback_queue_]
↓
[AudioOutputTask - 播放PCM数据]
↓
[扬声器输出]
关键技术要点
- 事件驱动架构:使用FreeRTOS事件组控制音频流程
- 多任务协作:AudioInputTask、OpusCodecTask、AudioOutputTask三个任务协同工作
- 队列管理:5个不同用途的队列管理音频数据流
- Opus编解码:60ms帧长度,16kHz采样率的Opus压缩
- 双声道处理:自动提取左声道数据进行处理
- 重采样:支持不同采样率之间的转换
- 内存管理:使用智能指针和移动语义优化内存使用
这个流程确保了音频测试功能能够高质量地录制、编码、存储、解码和播放音频数据。
AS_EVENT_AUDIO_TESTING_RUNNING从按键触发到录音到播放的所有代码流程:
完整流程概述
1. 按键触发阶段
- GPIO0 (BOOT按钮) → Button::OnClick() → Application::ToggleChatState()
- 在WiFi配置状态下,按键会启用音频测试功能
2. 录音启用阶段
- AudioService::EnableAudioTesting(true) 设置 AS_EVENT_AUDIO_TESTING_RUNNING 事件位
- AudioInputTask 检测到事件并开始录音循环
3. 录音处理阶段
- ReadAudioData() 从麦克风读取PCM数据(24kHz采样率)
- 双声道数据转换为单声道(提取左声道)
- 重采样到16kHz
- PushTaskToEncodeQueue() 推送到编码队列
4. Opus编码阶段
- OpusCodecTask 处理编码任务
- 使用60ms帧长度进行Opus编码
- 编码后的数据存储到 audio_testing_queue_
- 最大录音时长10秒(队列满时自动停止)
5. 播放触发阶段
- 再次按键调用 EnableAudioTesting(false)
- audio_testing_queue_ 移动到 audio_decode_queue_ 触发播放
6. Opus解码阶段
- OpusCodecTask 处理解码任务
- Opus解码为PCM数据
- 重采样到输出采样率(24kHz)
- 推送到 audio_playback_queue_
7. 音频播放阶段
- AudioOutputTask 从播放队列取出PCM数据
- codec_->OutputData() 输出到扬声器
关键技术特点
- 事件驱动架构:使用FreeRTOS事件组控制音频流程
- 多任务协作:三个专门的音频处理任务协同工作
- 队列管理:5个不同用途的队列管理音频数据流
- Opus编解码:高效的音频压缩确保音质
- 采样率处理:支持24kHz输入/输出和16kHz编码的转换
- 内存优化:使用智能指针和移动语义
这个完整的流程确保了音频测试功能能够高质量地录制、编码、存储、解码和播放音频数据,为用户提供清晰的音频回放体验。
更多推荐


所有评论(0)