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数据]
    ↓
[扬声器输出]

关键技术要点

  1. 事件驱动架构:使用FreeRTOS事件组控制音频流程
  2. 多任务协作:AudioInputTask、OpusCodecTask、AudioOutputTask三个任务协同工作
  3. 队列管理:5个不同用途的队列管理音频数据流
  4. Opus编解码:60ms帧长度,16kHz采样率的Opus压缩
  5. 双声道处理:自动提取左声道数据进行处理
  6. 重采样:支持不同采样率之间的转换
  7. 内存管理:使用智能指针和移动语义优化内存使用

这个流程确保了音频测试功能能够高质量地录制、编码、存储、解码和播放音频数据。
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() 输出到扬声器

关键技术特点

  1. 事件驱动架构:使用FreeRTOS事件组控制音频流程
  2. 多任务协作:三个专门的音频处理任务协同工作
  3. 队列管理:5个不同用途的队列管理音频数据流
  4. Opus编解码:高效的音频压缩确保音质
  5. 采样率处理:支持24kHz输入/输出和16kHz编码的转换
  6. 内存优化:使用智能指针和移动语义

这个完整的流程确保了音频测试功能能够高质量地录制、编码、存储、解码和播放音频数据,为用户提供清晰的音频回放体验。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐