mirror of
https://github.com/78/xiaozhi-esp32.git
synced 2026-07-21 02:05:52 +00:00
* Migrate board builds to ESP-IDF 6.0.1 * Document upstream IDF 6 CI blockers * Use IDF 6 patched registry components * Fix board-specific ESP-IDF 6 build errors * Fix remaining ESP-IDF 6 board builds * Document final ESP-IDF 6 matrix results [skip ci] * update IDF 6 component releases * rebase IDF 6 migration and use upstream SSCMA * Enhance audio input management in AudioService - Introduced AS_EVENT_AUDIO_INPUT_STOP_REQUEST to manage audio input stopping more effectively. - Updated AudioService::Start() to clear the new stop request event. - Modified AudioService::AudioInputTask() to handle the stop request and ensure proper input disabling. - Adjusted AdcPdmAudioCodec::Start() to reflect lazy input opening, ensuring start/stop operations run in the same task. * Refactor audio codec management and configuration - Added output_device_opened_ flag to track the state of the output device in Es8388 and Es8389 codecs. - Updated EnableOutput method to prevent unnecessary device reopening and added mute functionality to manage audio output more effectively. - Enhanced error handling in Es8311AudioCodec by ensuring proper closure and deletion of the codec device. - Adjusted sample rates in board configurations for atk-dnesp32s3-box2 to 24000 Hz and introduced AUDIO_CODEC_USE_MCLK for improved clock management. * Update build configurations and documentation for ESP-IDF 6 compatibility - Added container specification for the build workflow using espressif/idf:v6.0.1. - Updated the version of the espressif/esp_video component to ^2.3.0. - Enhanced documentation to clarify the support status of ESP32-P4 variants, specifying that legacy Rev < 3 variants are excluded from the IDF 6 matrix. - Adjusted board configurations to include IDF version constraints and necessary SDK configurations for legacy support. - Improved handling of YUV formats in EspVideo to maintain compatibility with previous versions. * Implement playback drained event handling in Application - Added MAIN_EVENT_PLAYBACK_DRAINED to manage playback state transitions. - Introduced callbacks for playback drained events in AudioService to trigger listening state changes. - Refactored Application::Run() to handle deferred listening start based on playback queue status. - Enhanced audio processing logic to prevent audio truncation during state changes. - Updated related methods to ensure proper wake word detection configuration during listening mode. * Fix variant selection shell in CI * Update project version to 2.4.0 and adjust component dependencies - Bump project version in CMakeLists.txt to 2.4.0. - Change espressif/esp_video component version to ^2.0.1 in idf_component.yml. - Modify AUDIO_INPUT_REFERENCE setting in config.h to false for m5stack-core-s3. - Remove unnecessary infinite loops in xmini_c3_board.cc during initialization. --------- Co-authored-by: Xiaoxia <terrence.huang@tenclass.com>
204 lines
7.8 KiB
C++
204 lines
7.8 KiB
C++
#ifndef AUDIO_SERVICE_H
|
|
#define AUDIO_SERVICE_H
|
|
|
|
#include <memory>
|
|
#include <deque>
|
|
#include <condition_variable>
|
|
#include <chrono>
|
|
#include <mutex>
|
|
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/task.h>
|
|
#include <freertos/event_groups.h>
|
|
#include <esp_timer.h>
|
|
#include <model_path.h>
|
|
#include "esp_audio_enc.h"
|
|
#include "esp_opus_enc.h"
|
|
#include "esp_opus_dec.h"
|
|
#include "esp_ae_rate_cvt.h"
|
|
#include "esp_audio_types.h"
|
|
|
|
#include "audio_codec.h"
|
|
#include "audio_debugger.h"
|
|
#include "audio_engine.h"
|
|
#include "protocol.h"
|
|
#include "ogg_demuxer.h"
|
|
|
|
/*
|
|
* There are two types of audio data flow:
|
|
* 1. (MIC) -> [Audio Engine] -> {Encode Queue} -> [Opus Encoder] -> {Send Queue} -> (Server)
|
|
* 2. (Server) -> {Decode Queue} -> [Opus Decoder] -> {Playback Queue} -> (Speaker)
|
|
*
|
|
* We use dedicated tasks for input, output, and Opus encoding/decoding.
|
|
*
|
|
* Decode Queue and Send Queue are the main queues, because Opus packets are quite smaller than PCM packets.
|
|
*
|
|
*/
|
|
|
|
#define OPUS_FRAME_DURATION_MS 60
|
|
#define MAX_ENCODE_TASKS_IN_QUEUE 2
|
|
#define MAX_PLAYBACK_TASKS_IN_QUEUE 2
|
|
#define MAX_DECODE_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS)
|
|
#define MAX_SEND_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS)
|
|
#define AUDIO_TESTING_MAX_DURATION_MS 10000
|
|
#define MAX_TIMESTAMPS_IN_QUEUE 3
|
|
|
|
#define AUDIO_POWER_TIMEOUT_MS 15000
|
|
#define AUDIO_POWER_CHECK_INTERVAL_MS 1000
|
|
|
|
#define AS_EVENT_AUDIO_TESTING_RUNNING (1 << 0)
|
|
#define AS_EVENT_WAKE_WORD_RUNNING (1 << 1)
|
|
#define AS_EVENT_AUDIO_PROCESSOR_RUNNING (1 << 2)
|
|
#define AS_EVENT_PLAYBACK_NOT_EMPTY (1 << 3)
|
|
#define AS_EVENT_AUDIO_INPUT_STOP_REQUEST (1 << 4)
|
|
|
|
#define AS_OPUS_GET_FRAME_DRU_ENUM(duration_ms) \
|
|
((duration_ms) == 5 ? ESP_OPUS_ENC_FRAME_DURATION_5_MS : \
|
|
(duration_ms) == 10 ? ESP_OPUS_ENC_FRAME_DURATION_10_MS : \
|
|
(duration_ms) == 20 ? ESP_OPUS_ENC_FRAME_DURATION_20_MS : \
|
|
(duration_ms) == 40 ? ESP_OPUS_ENC_FRAME_DURATION_40_MS : \
|
|
(duration_ms) == 60 ? ESP_OPUS_ENC_FRAME_DURATION_60_MS : \
|
|
(duration_ms) == 80 ? ESP_OPUS_ENC_FRAME_DURATION_80_MS : \
|
|
(duration_ms) == 100 ? ESP_OPUS_ENC_FRAME_DURATION_100_MS : \
|
|
(duration_ms) == 120 ? ESP_OPUS_ENC_FRAME_DURATION_120_MS : -1)
|
|
|
|
#define AS_OPUS_ENC_CONFIG() { \
|
|
.sample_rate = ESP_AUDIO_SAMPLE_RATE_16K, \
|
|
.channel = ESP_AUDIO_MONO, \
|
|
.bits_per_sample = ESP_AUDIO_BIT16, \
|
|
.bitrate = ESP_OPUS_BITRATE_AUTO, \
|
|
.frame_duration = (esp_opus_enc_frame_duration_t)AS_OPUS_GET_FRAME_DRU_ENUM(OPUS_FRAME_DURATION_MS), \
|
|
.application_mode = ESP_OPUS_ENC_APPLICATION_AUDIO, \
|
|
.complexity = 0, \
|
|
.enable_fec = false, \
|
|
.enable_dtx = true, \
|
|
.enable_vbr = true, \
|
|
}
|
|
|
|
struct AudioServiceCallbacks {
|
|
std::function<void(void)> on_send_queue_available;
|
|
std::function<void(const std::string&)> on_wake_word_detected;
|
|
std::function<void(bool)> on_vad_change;
|
|
std::function<void(void)> on_audio_testing_queue_full;
|
|
// Fired when both the decode and playback queues become empty
|
|
std::function<void(void)> on_playback_drained;
|
|
};
|
|
|
|
|
|
enum AudioTaskType {
|
|
kAudioTaskTypeEncodeToSendQueue,
|
|
kAudioTaskTypeEncodeToTestingQueue,
|
|
kAudioTaskTypeDecodeToPlaybackQueue,
|
|
};
|
|
|
|
struct AudioTask {
|
|
AudioTaskType type;
|
|
std::vector<int16_t> pcm;
|
|
uint32_t timestamp;
|
|
};
|
|
|
|
struct DebugStatistics {
|
|
uint32_t input_count = 0;
|
|
uint32_t decode_count = 0;
|
|
uint32_t encode_count = 0;
|
|
uint32_t playback_count = 0;
|
|
uint32_t encode_drop_count = 0;
|
|
};
|
|
|
|
class AudioService {
|
|
public:
|
|
AudioService();
|
|
~AudioService();
|
|
|
|
void Initialize(AudioCodec* codec);
|
|
void Start();
|
|
void Stop();
|
|
void EncodeWakeWord();
|
|
std::unique_ptr<AudioStreamPacket> PopWakeWordPacket();
|
|
const std::string& GetLastWakeWord() const;
|
|
bool IsVoiceDetected() const { return voice_detected_; }
|
|
bool IsIdle();
|
|
bool IsPlaybackIdle();
|
|
bool IsWakeWordRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_WAKE_WORD_RUNNING; }
|
|
bool IsAudioProcessorRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_AUDIO_PROCESSOR_RUNNING; }
|
|
bool IsAfeWakeWord();
|
|
|
|
void EnableWakeWordDetection(bool enable);
|
|
void EnableVoiceProcessing(bool enable);
|
|
void EnableAudioTesting(bool enable);
|
|
void EnableDeviceAec(bool enable);
|
|
|
|
void SetCallbacks(AudioServiceCallbacks& callbacks);
|
|
|
|
bool PushPacketToDecodeQueue(std::unique_ptr<AudioStreamPacket> packet, bool wait = false);
|
|
std::unique_ptr<AudioStreamPacket> PopPacketFromSendQueue();
|
|
void PlaySound(const std::string_view& sound);
|
|
bool ReadAudioData(std::vector<int16_t>& data, int sample_rate, int samples);
|
|
void ResetDecoder();
|
|
void SetModelsList(srmodel_list_t* models_list);
|
|
|
|
private:
|
|
AudioCodec* codec_ = nullptr;
|
|
AudioServiceCallbacks callbacks_;
|
|
std::unique_ptr<AudioEngine> audio_engine_;
|
|
std::unique_ptr<AudioDebugger> audio_debugger_;
|
|
void* opus_encoder_ = nullptr;
|
|
void* opus_decoder_ = nullptr;
|
|
std::mutex decoder_mutex_;
|
|
std::mutex input_resampler_mutex_;
|
|
esp_ae_rate_cvt_handle_t input_resampler_ = nullptr;
|
|
esp_ae_rate_cvt_handle_t output_resampler_ = nullptr;
|
|
|
|
// Encoder/Decoder state
|
|
int encoder_sample_rate_ = 16000;
|
|
int encoder_duration_ms_ = OPUS_FRAME_DURATION_MS;
|
|
int encoder_frame_size_ = 0;
|
|
int encoder_outbuf_size_ = 0;
|
|
int decoder_sample_rate_ = 0;
|
|
int decoder_duration_ms_ = OPUS_FRAME_DURATION_MS;
|
|
int decoder_frame_size_ = 0;
|
|
DebugStatistics debug_statistics_;
|
|
int64_t last_encode_drop_log_time_ = 0;
|
|
srmodel_list_t* models_list_ = nullptr;
|
|
|
|
EventGroupHandle_t event_group_;
|
|
|
|
// Audio encode / decode
|
|
TaskHandle_t audio_input_task_handle_ = nullptr;
|
|
TaskHandle_t audio_output_task_handle_ = nullptr;
|
|
TaskHandle_t opus_codec_task_handle_ = nullptr;
|
|
std::mutex audio_queue_mutex_;
|
|
std::condition_variable audio_queue_cv_;
|
|
std::deque<std::unique_ptr<AudioStreamPacket>> audio_decode_queue_;
|
|
std::deque<std::unique_ptr<AudioStreamPacket>> audio_send_queue_;
|
|
std::deque<std::unique_ptr<AudioStreamPacket>> audio_testing_queue_;
|
|
std::deque<std::unique_ptr<AudioTask>> audio_encode_queue_;
|
|
std::deque<std::unique_ptr<AudioTask>> audio_playback_queue_;
|
|
// For server AEC
|
|
std::deque<uint32_t> timestamp_queue_;
|
|
|
|
bool audio_engine_initialized_ = false;
|
|
bool voice_detected_ = false;
|
|
#if CONFIG_USE_DEVICE_AEC
|
|
bool device_aec_enabled_ = true;
|
|
#else
|
|
bool device_aec_enabled_ = false;
|
|
#endif
|
|
bool service_stopped_ = true;
|
|
bool audio_input_need_warmup_ = false;
|
|
|
|
esp_timer_handle_t audio_power_timer_ = nullptr;
|
|
std::chrono::steady_clock::time_point last_input_time_;
|
|
std::chrono::steady_clock::time_point last_output_time_;
|
|
|
|
void AudioInputTask();
|
|
void AudioOutputTask();
|
|
void OpusCodecTask();
|
|
void PushTaskToEncodeQueue(AudioTaskType type, std::vector<int16_t>&& pcm);
|
|
bool InitializeAudioEngine();
|
|
void SetDecodeSampleRate(int sample_rate, int frame_duration);
|
|
void CheckAndUpdateAudioPowerState();
|
|
};
|
|
|
|
#endif
|