From e52b40b010684e74efd1ecb7d9c407a145d71d39 Mon Sep 17 00:00:00 2001 From: hcj <1224667075@qq.com> Date: Mon, 5 Jan 2026 18:17:58 +0800 Subject: [PATCH] =?utf8?q?=E5=A2=9E=E5=8A=A0=E9=9B=B7=E9=B8=9Ftts=E4=B8=8D?= =?utf8?q?=E5=90=8C=E6=AF=94=E7=89=B9=E7=8E=87=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../zhihui/ai/media/ai_media_service_ext.cpp | 52 ++- .../zhihui/leiniao/imsl_ln/ln_api_websocket.c | 305 ++++++++++++++++++ 2 files changed, 355 insertions(+), 2 deletions(-) diff --git a/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp b/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp index 8badf5102..8eae31e77 100644 --- a/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp +++ b/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp @@ -283,6 +283,9 @@ typedef struct { static MediaMutexHandle g_aiVoiceContextMutex = nullptr; static AIVoiceContext *g_aiVoiceContext = nullptr; +// TTS音频比特率(默认64000 bps) +static uint32_t g_tts_audio_bit_rate = 64000; + // 文件缓存相关变量 static const char* AI_AUDIO_CACHE_FILE = "/user/ai_audio_cache.dat"; static const size_t AI_AUDIO_CHUNK_SIZE = 1024; // 固定音频块大小:1KB @@ -2120,11 +2123,26 @@ int32_t AI_WriteAudioData(const uint8_t* data, uint32_t size) // 写入音频数据到输出流 int32_t ret = AudioStreamOutStreamWrite(data, size); - usleep(40*size); + + // 根据比特率动态计算延迟时间 + // 延迟时间(微秒)= (数据大小(字节)* 8 * 1000000) / 比特率(bps) + // 当比特率更高时,延迟时间更短 + // 公式:延迟(微秒)= (size * 8 * 1000000) / bit_rate + uint32_t delay_us = (uint32_t)((uint64_t)(size-(size/10*3)) * 8 * 1000000ULL / g_tts_audio_bit_rate); + printf("delay_us %d\r\n",delay_us); + + // 确保延迟时间在合理范围内(最小10微秒,最大100毫秒) + if (delay_us < 20) { + delay_us = 20; + } else if (delay_us > 100000) { + delay_us = 100000; + } + + usleep(delay_us-20); if (ret != 0) { retry_count++; // MEDIA_ERR_LOG("Failed to write audio data to output stream, ret=%d, retry %d/%d", ret, retry_count, MAX_RETRY_COUNT); - usleep(1000*300); + usleep(delay_us*5); goto AI_WRITE; } @@ -2137,6 +2155,36 @@ int32_t AI_WriteAudioData(const uint8_t* data, uint32_t size) return 0; } +/** + * @brief 设置TTS音频比特率 + * @param bit_rate 比特率(bps),如果为0则使用默认值64000 + * @return 0 成功,-1 失败(bit_rate无效) + */ +int32_t AI_SetTTSAudioBitRate(uint32_t bit_rate) +{ + if (bit_rate == 0) { + // 使用默认值 + g_tts_audio_bit_rate = 64000; + } else if (bit_rate > 0 && bit_rate <= 10000000) { // 限制范围:1 bps 到 10 Mbps + g_tts_audio_bit_rate = bit_rate; + } else { + MEDIA_ERR_LOG("Invalid bit rate: %u, must be between 1 and 1000000", bit_rate); + return -1; + } + + MEDIA_INFO_LOG("TTS audio bit rate set to: %u bps", g_tts_audio_bit_rate); + return 0; +} + +/** + * @brief 获取TTS音频比特率 + * @return 当前比特率(bps) + */ +uint32_t AI_GetTTSAudioBitRate(void) +{ + return g_tts_audio_bit_rate; +} + /** * @brief 检查AI语音服务是否正在运行 * @return true 正在运行,false 未运行 diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c index 925c6e3f6..9895deed3 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "cJSON.h" // Added for JSON parsing #include "custom_utils.h" #include "soc_osal.h" @@ -118,6 +119,9 @@ int ln_ws_send_ping(void); static ws_command_t ln_ws_parse_command_string(const char *command_str); static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_msg); static void ln_ws_free_data_message(ws_data_msg_t *data_msg); +static int ln_ws_save_tts_audio_setting_to_file(void); +static int ln_ws_load_tts_audio_setting_from_file(void); +static uint32_t ln_ws_convert_audio_format_string_to_enum(const char *format_str); static void ln_ws_default_message_handler(const ws_data_msg_t *data_msg, ws_command_t type); static void ln_ws_process_message_with_callback(const ws_data_msg_t *data_msg); static int ln_ws_close_with_status(int client_fd); @@ -171,6 +175,18 @@ typedef struct { // 全局音频聊天上下文 static audio_chat_context_t g_audio_chat_ctx = {0}; +// TTS音频设置结构体 +typedef struct { + uint32_t audio_bit_rate; // 音频比特率 + uint32_t audio_sample_rate; // 音频采样率 + uint32_t audio_channel; // 音频声道数 + char audio_format[16]; // 音频格式(如 "mp3", "pcm") + bool valid; // 数据是否有效 +} tts_audio_setting_t; + +// 全局TTS音频设置 +static tts_audio_setting_t g_tts_audio_setting = {0}; + // 音频聊天结束标志 static bool g_audio_chat_ending = false; @@ -1320,11 +1336,40 @@ void ln_ws_audio_init(bool is_vad_server_enabled) .sampleRate = 16000 }; + // 从文件加载TTS音频设置 + ln_ws_load_tts_audio_setting_from_file(); + + // 初始化audioParams_out,使用默认值 AIVoiceAudioParams audioParams_out = { .audioFormat = MP3, .channelCount = 1, .sampleRate = 16000 }; + + // 如果从文件加载了有效配置,则应用配置 + if (g_tts_audio_setting.valid) { + // 转换音频格式字符串为枚举值 + audioParams_out.audioFormat = ln_ws_convert_audio_format_string_to_enum(g_tts_audio_setting.audio_format); + + // 应用采样率(如果有效) + if (g_tts_audio_setting.audio_sample_rate > 0) { + audioParams_out.sampleRate = g_tts_audio_setting.audio_sample_rate; + } + + // 应用声道数(如果有效) + if (g_tts_audio_setting.audio_channel > 0) { + audioParams_out.channelCount = g_tts_audio_setting.audio_channel; + } + + AI_SetTTSAudioBitRate(g_tts_audio_setting.audio_bit_rate); + + LN_WS_INFO("Applied TTS audio settings: format=%u, sampleRate=%u, channelCount=%u\n", + audioParams_out.audioFormat, audioParams_out.sampleRate, audioParams_out.channelCount); + } else { + LN_WS_DEBUG("Using default audio parameters for audioParams_out\n"); + } + + #ifdef SUPPORT_CAT1_SERVICE cat1_api_keep_wakeup(1); @@ -1936,6 +1981,8 @@ static ws_command_t ln_ws_parse_command_string(const char *command_str) return WS_CMD_CANCEL_DOWNLOAD_EMOJI; } else if (strcmp(command_str, "CancelDownloadEmojiAck") == 0) { return WS_CMD_CANCEL_DOWNLOAD_EMOJI_ACK; + } else if (strcmp(command_str, "TTSAudioSetting") == 0) { + return WS_CMD_TTS_AUDIO_SETTING; } else { return WS_CMD_UNKNOWN; } @@ -2132,6 +2179,64 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->audio_url, data_msg->text_content, data_msg->audio_duration); } + // 解析TTSAudioSetting特定字段 + if (data_msg->command == WS_CMD_TTS_AUDIO_SETTING) { + cJSON *data_obj = cJSON_GetObjectItem(root, "data"); + if (data_obj && cJSON_IsObject(data_obj)) { + // 清空之前的设置 + memset(&g_tts_audio_setting, 0, sizeof(tts_audio_setting_t)); + + // 解析audioBitRate字段 + cJSON *audio_bit_rate = cJSON_GetObjectItem(data_obj, "audioBitRate"); + if (audio_bit_rate && cJSON_IsNumber(audio_bit_rate)) { + g_tts_audio_setting.audio_bit_rate = (uint32_t)audio_bit_rate->valuedouble; + } else { + LN_WS_WARN("Missing or invalid audioBitRate in TTSAudioSetting\n"); + } + + // 解析audioSampleRate字段 + cJSON *audio_sample_rate = cJSON_GetObjectItem(data_obj, "audioSampleRate"); + if (audio_sample_rate && cJSON_IsNumber(audio_sample_rate)) { + g_tts_audio_setting.audio_sample_rate = (uint32_t)audio_sample_rate->valuedouble; + } else { + LN_WS_WARN("Missing or invalid audioSampleRate in TTSAudioSetting\n"); + } + + // 解析audioChannel字段 + cJSON *audio_channel = cJSON_GetObjectItem(data_obj, "audioChannel"); + if (audio_channel && cJSON_IsNumber(audio_channel)) { + g_tts_audio_setting.audio_channel = (uint32_t)audio_channel->valuedouble; + } else { + LN_WS_WARN("Missing or invalid audioChannel in TTSAudioSetting\n"); + } + + // 解析audioFormat字段 + cJSON *audio_format = cJSON_GetObjectItem(data_obj, "audioFormat"); + if (audio_format && cJSON_IsString(audio_format) && audio_format->valuestring) { + strncpy(g_tts_audio_setting.audio_format, audio_format->valuestring, sizeof(g_tts_audio_setting.audio_format) - 1); + g_tts_audio_setting.audio_format[sizeof(g_tts_audio_setting.audio_format) - 1] = '\0'; + } else { + LN_WS_WARN("Missing or invalid audioFormat in TTSAudioSetting\n"); + } + + // 标记数据有效 + g_tts_audio_setting.valid = true; + + // 保存配置到文件 + if (ln_ws_save_tts_audio_setting_to_file() == 0) { + LN_WS_DEBUG("TTSAudioSetting saved to file successfully\n"); + } else { + LN_WS_WARN("Failed to save TTSAudioSetting to file\n"); + } + } else { + LN_WS_WARN("Missing or invalid data object in TTSAudioSetting\n"); + } + + LN_WS_DEBUG("TTSAudioSetting parsed: bitRate=%u, sampleRate=%u, channel=%u, format=%s\n", + g_tts_audio_setting.audio_bit_rate, g_tts_audio_setting.audio_sample_rate, + g_tts_audio_setting.audio_channel, g_tts_audio_setting.audio_format); + } + // 解析ChooseStoryResponse特定字段 if (data_msg->command == WS_CMD_CHOOSE_STORY_RESPONSE) { cJSON *data_obj = cJSON_GetObjectItem(root, "data"); @@ -2450,6 +2555,18 @@ static void ln_ws_default_message_handler(const ws_data_msg_t *data_msg, ws_comm LN_WS_DEBUG("Story Audio URL: %s\n", data_msg->audio_url); } break; + case WS_CMD_TTS_AUDIO_SETTING: + if (data_msg) { + LN_WS_DEBUG("Default: Processing TTSAudioSetting message\n"); + if (g_tts_audio_setting.valid) { + LN_WS_DEBUG("TTSAudioSetting details: bitRate=%u, sampleRate=%u, channel=%u, format=%s\n", + g_tts_audio_setting.audio_bit_rate, g_tts_audio_setting.audio_sample_rate, + g_tts_audio_setting.audio_channel, g_tts_audio_setting.audio_format); + } else { + LN_WS_WARN("TTSAudioSetting data is invalid\n"); + } + } + break; case WS_CMD_UNKNOWN: if (data_msg) { LN_WS_DEBUG("Default: Processing unknown command message\n"); @@ -3819,4 +3936,192 @@ int ln_ws_send_sleep_mode_inquire(const char *intention) } return ret; +} + +/** + * @brief 保存TTS音频设置到文件 + * @return 0 成功,-1 失败 + */ +static int ln_ws_save_tts_audio_setting_to_file(void) +{ + if (!g_tts_audio_setting.valid) { + LN_WS_WARN("TTS audio setting is invalid, cannot save to file\n"); + return -1; + } + + const char *file_path = "/user/tts_audio_setting.json"; + + // 创建JSON对象 + cJSON *root = cJSON_CreateObject(); + if (!root) { + LN_WS_ERROR("Failed to create JSON object for TTS audio setting\n"); + return -1; + } + + cJSON_AddNumberToObject(root, "audioBitRate", g_tts_audio_setting.audio_bit_rate); + cJSON_AddNumberToObject(root, "audioSampleRate", g_tts_audio_setting.audio_sample_rate); + cJSON_AddNumberToObject(root, "audioChannel", g_tts_audio_setting.audio_channel); + cJSON_AddStringToObject(root, "audioFormat", g_tts_audio_setting.audio_format); + + // 转换为JSON字符串 + char *json_str = cJSON_Print(root); + if (!json_str) { + LN_WS_ERROR("Failed to create JSON string for TTS audio setting\n"); + cJSON_Delete(root); + return -1; + } + + // 写入文件 + FILE *fp = fopen(file_path, "w"); + if (fp == NULL) { + LN_WS_ERROR("Failed to open file for writing: %s\n", file_path); + cJSON_free(json_str); + cJSON_Delete(root); + return -1; + } + + size_t len = strlen(json_str); + size_t written = fwrite(json_str, 1, len, fp); + fclose(fp); + + cJSON_free(json_str); + cJSON_Delete(root); + + if (written != len) { + LN_WS_ERROR("Failed to write all data to file: %s, written: %zu, expected: %zu\n", + file_path, written, len); + return -1; + } + + LN_WS_DEBUG("TTS audio setting saved to file: %s\n", file_path); + return 0; +} + +/** + * @brief 从文件加载TTS音频设置 + * @return 0 成功,-1 失败 + */ +static int ln_ws_load_tts_audio_setting_from_file(void) +{ + const char *file_path = "/user/tts_audio_setting.json"; + + // 打开文件 + FILE *fp = fopen(file_path, "r"); + if (fp == NULL) { + LN_WS_DEBUG("TTS audio setting file not found: %s, using default values\n", file_path); + return -1; + } + + // 获取文件大小 + fseek(fp, 0, SEEK_END); + long file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + if (file_size <= 0 || file_size > 1024) { // 限制文件大小 + LN_WS_WARN("Invalid file size: %ld\n", file_size); + fclose(fp); + return -1; + } + + // 读取文件内容 + char *buffer = (char *)malloc(file_size + 1); + if (!buffer) { + LN_WS_ERROR("Failed to allocate memory for file content\n"); + fclose(fp); + return -1; + } + + size_t read_size = fread(buffer, 1, file_size, fp); + fclose(fp); + + if (read_size != (size_t)file_size) { + LN_WS_WARN("Failed to read all data from file: %s\n", file_path); + free(buffer); + return -1; + } + + buffer[file_size] = '\0'; + + // 解析JSON + cJSON *root = cJSON_Parse(buffer); + free(buffer); + + if (!root) { + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) { + LN_WS_ERROR("JSON parse error: %s\n", error_ptr); + } + return -1; + } + + // 清空之前的设置 + memset(&g_tts_audio_setting, 0, sizeof(tts_audio_setting_t)); + + // 解析字段 + cJSON *audio_bit_rate = cJSON_GetObjectItem(root, "audioBitRate"); + if (audio_bit_rate && cJSON_IsNumber(audio_bit_rate)) { + g_tts_audio_setting.audio_bit_rate = (uint32_t)audio_bit_rate->valuedouble; + } + + cJSON *audio_sample_rate = cJSON_GetObjectItem(root, "audioSampleRate"); + if (audio_sample_rate && cJSON_IsNumber(audio_sample_rate)) { + g_tts_audio_setting.audio_sample_rate = (uint32_t)audio_sample_rate->valuedouble; + } + + cJSON *audio_channel = cJSON_GetObjectItem(root, "audioChannel"); + if (audio_channel && cJSON_IsNumber(audio_channel)) { + g_tts_audio_setting.audio_channel = (uint32_t)audio_channel->valuedouble; + } + + cJSON *audio_format = cJSON_GetObjectItem(root, "audioFormat"); + if (audio_format && cJSON_IsString(audio_format) && audio_format->valuestring) { + strncpy(g_tts_audio_setting.audio_format, audio_format->valuestring, + sizeof(g_tts_audio_setting.audio_format) - 1); + g_tts_audio_setting.audio_format[sizeof(g_tts_audio_setting.audio_format) - 1] = '\0'; + } + + // 标记数据有效 + g_tts_audio_setting.valid = true; + + cJSON_Delete(root); + + LN_WS_DEBUG("TTS audio setting loaded from file: %s\n", file_path); + LN_WS_DEBUG("Loaded settings: bitRate=%u, sampleRate=%u, channel=%u, format=%s\n", + g_tts_audio_setting.audio_bit_rate, g_tts_audio_setting.audio_sample_rate, + g_tts_audio_setting.audio_channel, g_tts_audio_setting.audio_format); + + return 0; +} + +/** + * @brief 将音频格式字符串转换为枚举值 + * @param format_str 格式字符串(如 "mp3", "pcm") + * @return AudioCodecFormat 枚举值,如果无效则返回 MP3(默认值) + */ +static uint32_t ln_ws_convert_audio_format_string_to_enum(const char *format_str) +{ + if (!format_str) { + return MP3; // 默认值 + } + + // 转换为小写进行比较 + char lower_str[16] = {0}; + for (int i = 0; i < (int)sizeof(lower_str) - 1 && format_str[i] != '\0'; i++) { + lower_str[i] = (char)tolower((unsigned char)format_str[i]); + } + + if (strcmp(lower_str, "mp3") == 0) { + return MP3; + } else if (strcmp(lower_str, "pcm") == 0) { + return PCM; + } else if (strcmp(lower_str, "aac") == 0 || strcmp(lower_str, "aac_lc") == 0) { + return AAC_LC; + } else if (strcmp(lower_str, "opus") == 0) { + return OPUS; + } else if (strcmp(lower_str, "flac") == 0) { + return FLAC; + } else { + LN_WS_WARN("Unknown audio format: %s, using default MP3\n", format_str); + return MP3; // 默认值 + } } \ No newline at end of file -- 2.17.1