From 9f28f4512c19d3c38c2896be077e723f28a102f3 Mon Sep 17 00:00:00 2001 From: hcj <1224667075@qq.com> Date: Thu, 25 Sep 2025 21:29:58 +0800 Subject: [PATCH] =?utf8?q?=E4=BF=AE=E6=94=B9ai=E9=9F=B3=E9=A2=91=E5=87=BD?= =?utf8?q?=E6=95=B0=EF=BC=8C=E4=BD=BF=E6=94=AF=E6=8C=81=E5=8F=82=E6=95=B0?= =?utf8?q?=E8=BE=93=E5=85=A5=EF=BC=8C=E5=8F=8Aogg=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- code/sdk/zhihui/ai/include/ai_media.h | 9 +- code/sdk/zhihui/ai/media/CMakeLists.txt | 2 + .../zhihui/ai/media/ai_media_service_ext.cpp | 531 ++++++++++++++++-- .../zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp | 18 +- code/sdk/zhihui/ai_old/bk_websocket.h | 17 +- .../ai_old/websocket_server_example.cpp | 25 +- .../ble_service/blemoder/bleInstruct.cpp | 8 +- 7 files changed, 560 insertions(+), 50 deletions(-) diff --git a/code/sdk/zhihui/ai/include/ai_media.h b/code/sdk/zhihui/ai/include/ai_media.h index 433d41925..d070e434d 100644 --- a/code/sdk/zhihui/ai/include/ai_media.h +++ b/code/sdk/zhihui/ai/include/ai_media.h @@ -90,6 +90,8 @@ uint8_t ai_media_get_play_status(void); void ai_media_play_buffer(uint8_t *data, uint32_t size); typedef void (*AIVoiceDataCallback)(const uint8_t* data, uint32_t size, void* userData); +typedef void (*AIVoiceStartCallback)(void* userData); +typedef void (*AIVoiceEndCallback)(void* userData); // AI语音音频参数结构体 // AI语音音频参数结构体 @@ -101,7 +103,12 @@ typedef struct { int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData, - const AIVoiceAudioParams* audioParams); + const AIVoiceAudioParams* inputAudioParams, + const AIVoiceAudioParams* outputAudioParams, + bool useOggEncapsulation); + +int32_t AI_StartOggEncapsulation(void); +int32_t AI_EndOggEncapsulation(void); /* * @brief 写入音频数据到输出流 diff --git a/code/sdk/zhihui/ai/media/CMakeLists.txt b/code/sdk/zhihui/ai/media/CMakeLists.txt index f93f44504..878be8cfd 100644 --- a/code/sdk/zhihui/ai/media/CMakeLists.txt +++ b/code/sdk/zhihui/ai/media/CMakeLists.txt @@ -17,6 +17,8 @@ ${ROOT_DIR}/middleware/services/media/foundation/audiomanager/frameworks/audiost ${ROOT_DIR}/middleware/services/media/foundation/sample/wrapper ${ROOT_DIR}/middleware/services/media/foundation/utils/include ${ROOT_DIR}/include +${ROOT_DIR}/open_source/libogg/libogg-1.3.5/include/ogg +${ROOT_DIR}/open_source/libogg/libogg-1.3.5/include ) set(AI_SOURCES 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 eb821152a..455c1db2c 100644 --- a/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp +++ b/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp @@ -30,7 +30,8 @@ #include "thread_init.h" #include "errcode.h" #include "ai_media.h" - +#include "ogg.h" +#include "cmsis_os2.h" #ifdef __cplusplus extern "C" { #endif @@ -48,11 +49,25 @@ const int32_t SAMPLERATE_16000 = 16000; const uint32_t AI_VOICE_BUFFER_SIZE = 0x800; // 2KB buffer for AI voice const uint32_t AI_VOICE_QUEUE_SIZE = 128; // 队列大小 +// OGG封装相关常量 +const uint32_t MILLSECOND_TO_SECOND = 1000; +const uint32_t HEADER_STRLEN = 8; +const uint32_t OPUS_DELAY_TIME_DIVISOR = 0; +const uint32_t OPUS_DELAY_TIME_DIVIDED = 100; +const uint32_t OPUS_ID_HEADER_LEN = 19; +const uint32_t OPUS_COMMENT_HEADER_LEN = 25; +const uint32_t OPUS_COMMENT_TAIL_LEN = 4; +const uint32_t OPUS_ENCODE_HEADER_SIZE = 8; +const uint32_t OPUS_OPUSTAGS_LEN = 12; +const uint32_t OPUS_SAMPLERATE = 48000; + // 默认音频参数 const AudioCodecFormat DEFAULT_AUDIO_FORMAT = MP3; const uint32_t DEFAULT_CHANNEL_COUNT = 1; const uint32_t DEFAULT_SAMPLE_RATE = SAMPLERATE_16000; +static MediaMutexHandle GetGlobalMutex(void); + // AI语音音频参数结构体IVoiceAudioParams; // 定义音频数据回调函数类型 @@ -64,6 +79,20 @@ typedef struct { uint32_t timestamp; } AIVoiceQueueMessage; +// OGG封装上下文结构体 +typedef struct CaptureOggContext{ + ogg_stream_state oggStream; + int64_t sampleRate; + int64_t channels; + int64_t granulepos; + int64_t packetCount; + int32_t serialno; + bool isEnd; + ogg_page *page; + ogg_packet *packet; + unsigned char *idHeader; + unsigned char *commentHeader; +} CaptureOggContext; typedef struct { AudioSession sessionId; @@ -78,15 +107,363 @@ typedef struct { void* callbackUserData; // 回调函数用户数据 osMessageQueueId_t inputQueue; // 输入播放数据队列 uint32_t queueMsgCount; // 队列消息数量 -// 音频参数 - AudioCodecFormat audioFormat; // 音频格式 - uint32_t channelCount; // 声道数 - uint32_t sampleRate; // 采样率 +// 输入流音频参数 + AudioCodecFormat inputAudioFormat; // 输入音频格式 + uint32_t inputChannelCount; // 输入声道数 + uint32_t inputSampleRate; // 输入采样率 + + // 输出流音频参数 + AudioCodecFormat outputAudioFormat; // 输出音频格式 + uint32_t outputChannelCount; // 输出声道数 + uint32_t outputSampleRate; // 输出采样率 + + // OGG封装相关 + CaptureOggContext oggContext; // OGG封装上下文 + bool useOggEncapsulation; // 是否使用OGG封装 + bool oggEncapsulationStarted; // OGG封装是否已开始 } AIVoiceContext; static MediaMutexHandle g_aiVoiceContextMutex = nullptr; static AIVoiceContext *g_aiVoiceContext = nullptr; +// OGG相关内存管理函数 +static void MediaMemFree(void **src) +{ + if (src == nullptr || *src == nullptr) { + MEDIA_ERR_LOG("%s", "don't need to free!"); + return; + } + free(*src); + *src = nullptr; +} + +static void *MediaMemCalloc(const size_t bytesNum) +{ + void *tmp = malloc(bytesNum); + + if (tmp == nullptr) { + MEDIA_ERR_LOG("%s", "malloc failed!"); + return nullptr; + } + + if (memset_s(tmp, bytesNum, 0, bytesNum) != EOK) { + MEDIA_ERR_LOG("%s", "memset_s failed!"); + MediaMemFree(&tmp); + return nullptr; + } + return tmp; +} + +// OGG头部初始化函数 +static int32_t AudioCapturerOggHeaderInit(CaptureOggContext *oggCtx, uint32_t channelCount, uint32_t sampleRate) +{ + oggCtx->idHeader = static_cast(MediaMemCalloc(OPUS_ID_HEADER_LEN)); + if (oggCtx->idHeader == nullptr) { + MEDIA_ERR_LOG("malloc failed!"); + return -1; + } + + if (memcpy_s(oggCtx->idHeader, HEADER_STRLEN, "OpusHead", HEADER_STRLEN) != 0) { + MEDIA_ERR_LOG("memcpy failed!"); + return -1; + } + oggCtx->idHeader[0x8] = 0x01; + oggCtx->idHeader[0x9] = channelCount; + uint32_t preSkip = sampleRate * OPUS_DELAY_TIME_DIVISOR / OPUS_DELAY_TIME_DIVIDED; + oggCtx->idHeader[0xA] = preSkip & 0xFF; + oggCtx->idHeader[0xB] = (preSkip >> 0x8); // 8 for Little-endian byte order + oggCtx->idHeader[0xC] = sampleRate & 0xFF; + oggCtx->idHeader[0xD] = (sampleRate >> 0x8) & 0xFF; // 8 for Little-endian byte order + oggCtx->idHeader[0xE] = (sampleRate >> 0x10) & 0xFF; // 16 for Little-endian byte order + oggCtx->idHeader[0xF] = (sampleRate >> 0x18) & 0xFF; // 24 for Little-endian byte order + + oggCtx->commentHeader = static_cast(MediaMemCalloc(OPUS_COMMENT_HEADER_LEN)); + if (oggCtx->commentHeader == nullptr) { + MEDIA_ERR_LOG("malloc failed!"); + return -1; + } + + if (memcpy_s(oggCtx->commentHeader, HEADER_STRLEN, "OpusTags", HEADER_STRLEN) != 0) { + MEDIA_ERR_LOG("Opus idHeader memcpy failed!"); + return -1; + } + const char *vendor = "libogg-1.3.5"; + uint32_t vendorLen = std::strlen(vendor); + oggCtx->commentHeader[0x8] = vendorLen & 0xFF; + oggCtx->commentHeader[0x9] = (vendorLen >> 0x8) & 0xFF; // 8 for Little-endian byte order + oggCtx->commentHeader[0xA] = (vendorLen >> 0x10) & 0xFF; // 16 for Little-endian byte order + oggCtx->commentHeader[0xB] = (vendorLen >> 0x18) & 0xFF; // 24 for Little-endian byte order + + if (memcpy_s(oggCtx->commentHeader + OPUS_OPUSTAGS_LEN, vendorLen, vendor, vendorLen) != 0) { + MEDIA_ERR_LOG("Opus commentHeader memcpy failed!"); + return -1; + } + return 0; +} + +// OGG上下文初始化函数 +static int32_t AudioCapturerOggCtxInit(CaptureOggContext *oggCtx, uint32_t channelCount, uint32_t sampleRate) +{ + oggCtx->packet = (ogg_packet *)MediaMemCalloc(sizeof(ogg_packet)); + if (oggCtx->packet == nullptr) { + MEDIA_ERR_LOG("malloc failed!"); + return -1; + } + + oggCtx->page = (ogg_page *)MediaMemCalloc(sizeof(ogg_page)); + if (oggCtx->page == nullptr) { + MEDIA_ERR_LOG("malloc failed!"); + return -1; + } + + oggCtx->sampleRate = sampleRate; + oggCtx->channels = channelCount; + oggCtx->granulepos = -1; // -1 means start page + oggCtx->packetCount = 0; + oggCtx->isEnd = false; + oggCtx->serialno = 0x1; // unique serialno + + if (ogg_stream_init(&oggCtx->oggStream, oggCtx->serialno) != 0) { + MEDIA_ERR_LOG("ogg stream init failed"); + return -1; + } + + if (AudioCapturerOggHeaderInit(oggCtx, channelCount, sampleRate) != 0) { + MEDIA_ERR_LOG("Header init failed!"); + return -1; + } + + // 写入ID头部 + oggCtx->packet->packet = oggCtx->idHeader; + oggCtx->packet->bytes = OPUS_ID_HEADER_LEN; + oggCtx->packet->b_o_s = 1; // 1 means begining of ogg stream + oggCtx->packet->e_o_s = 0; + oggCtx->packet->granulepos = oggCtx->granulepos; + oggCtx->packet->packetno = oggCtx->packetCount++; + oggCtx->granulepos = 0; + + int32_t idResult = ogg_stream_packetin(&oggCtx->oggStream, oggCtx->packet); + MEDIA_INFO_LOG("ID header packetin result: %d", idResult); + + // 写入注释头部 + oggCtx->packet->packet = oggCtx->commentHeader; + oggCtx->packet->bytes = OPUS_COMMENT_HEADER_LEN; + oggCtx->packet->b_o_s = 0; + oggCtx->packet->e_o_s = 0; + oggCtx->packet->granulepos = 0; + oggCtx->packet->packetno = oggCtx->packetCount++; + + int32_t commentResult = ogg_stream_packetin(&oggCtx->oggStream, oggCtx->packet); + MEDIA_INFO_LOG("Comment header packetin result: %d", commentResult); + + return 0; +} + +// OGG流刷新函数(修改为调用数据回调) +static int32_t AudioCaptureOggStreamFlush(CaptureOggContext *oggCtx, AIVoiceDataCallback dataCallback, void* userData) +{ + MEDIA_ERR_LOG("AudioCaptureOggStreamFlush"); + while (ogg_stream_flush(&oggCtx->oggStream, oggCtx->page)) { + // 计算页面总大小 + uint32_t pageSize = oggCtx->page->header_len + oggCtx->page->body_len; + uint8_t *pageData = (uint8_t*)malloc(pageSize); + + if (pageData != nullptr) { + // 复制页面头部 + memcpy(pageData, oggCtx->page->header, oggCtx->page->header_len); + // 复制页面体 + memcpy(pageData + oggCtx->page->header_len, oggCtx->page->body, oggCtx->page->body_len); + + // 调用数据回调函数 + if (dataCallback != nullptr) { + dataCallback(pageData, pageSize, userData); + } + + free(pageData); + } else { + MEDIA_ERR_LOG("Failed to allocate memory for OGG page"); + return -1; + } + } + return 0; +} + +// OGG页面写入函数(修改为调用数据回调) +static int32_t AudioCaptureWritePage(CaptureOggContext *oggCtx, AIVoiceDataCallback dataCallback, void* userData) +{ + if (ogg_stream_pageout(&oggCtx->oggStream, oggCtx->page)) { + // 计算页面总大小 + uint32_t pageSize = oggCtx->page->header_len + oggCtx->page->body_len; + uint8_t *pageData = (uint8_t*)malloc(pageSize); + + if (pageData != nullptr) { + // 复制页面头部 + memcpy(pageData, oggCtx->page->header, oggCtx->page->header_len); + // 复制页面体 + memcpy(pageData + oggCtx->page->header_len, oggCtx->page->body, oggCtx->page->body_len); + + // 调用数据回调函数 + if (dataCallback != nullptr) { + dataCallback(pageData, pageSize, userData); + } + + free(pageData); + } else { + MEDIA_ERR_LOG("Failed to allocate memory for OGG page"); + return -1; + } + } + else + { + // MEDIA_ERR_LOG("AudioCaptureWritePage"); + } + return 0; +} + +// OGG写入函数(修改为调用数据回调) +static int32_t AudioCapturerWriteInOGG(CaptureOggContext *oggCtx, uint8_t *buffer, int64_t bufferLen, + AIVoiceDataCallback dataCallback, void* userData, bool isEos) +{ + if (bufferLen < 0) { + MEDIA_ERR_LOG("input buffer invalid"); + return -1; + } + + // 检查缓冲区大小是否足够 + if (bufferLen < OPUS_ENCODE_HEADER_SIZE) { + MEDIA_ERR_LOG("Input buffer too small for OPUS header: %d < %d", bufferLen, OPUS_ENCODE_HEADER_SIZE); + return -1; + } + + uint32_t opusDataSize = bufferLen - OPUS_ENCODE_HEADER_SIZE; + + oggCtx->packet->packet = (unsigned char *)(buffer + OPUS_ENCODE_HEADER_SIZE); + oggCtx->packet->bytes = opusDataSize; + oggCtx->packet->b_o_s = 0; + oggCtx->packet->e_o_s = isEos ? 1 : 0; + oggCtx->packet->granulepos = oggCtx->granulepos; + oggCtx->packet->packetno = oggCtx->packetCount++; + oggCtx->granulepos += OPUS_SAMPLERATE * 10 / MILLSECOND_TO_SECOND; //capture frequency is 10ms for each frame + + int32_t packetinResult = ogg_stream_packetin(&oggCtx->oggStream, oggCtx->packet); + // MEDIA_INFO_LOG("ogg_stream_packetin result: %d", packetinResult); + + if (!isEos) { + return AudioCaptureWritePage(oggCtx, dataCallback, userData); + } else { + MEDIA_INFO_LOG("AudioCaptureOggStreamFlush"); + return AudioCaptureOggStreamFlush(oggCtx, dataCallback, userData); + } +} + +// OGG上下文清理函数 +static void AudioCapturerOggCtxDeInit(CaptureOggContext *oggCtx) +{ + if (oggCtx == nullptr) { + return; + } + + oggCtx->sampleRate = 0; + oggCtx->channels = 0; + oggCtx->granulepos = 0; + oggCtx->packetCount = 0; + MediaMemFree((void **)&oggCtx->packet); + MediaMemFree((void **)&oggCtx->page); + MediaMemFree((void **)&oggCtx->idHeader); + MediaMemFree((void **)&oggCtx->commentHeader); + ogg_stream_clear(&oggCtx->oggStream); +} + +// 开始OGG封装 +int32_t AI_StartOggEncapsulation(void) +{ + MediaMutexHandle mutex = GetGlobalMutex(); + if (mutex == nullptr) { + MEDIA_ERR_LOG("Failed to get global mutex"); + return -1; + } + + MediaMutexLock(mutex); + if (g_aiVoiceContext == nullptr) { + MEDIA_ERR_LOG("AI voice context is null"); + MediaMutexUnLock(mutex); + return -1; + } + + if (!g_aiVoiceContext->useOggEncapsulation) { + MEDIA_ERR_LOG("OGG encapsulation not enabled"); + MediaMutexUnLock(mutex); + return -1; + } + + if (g_aiVoiceContext->oggEncapsulationStarted) { + MEDIA_INFO_LOG("OGG encapsulation already started"); + MediaMutexUnLock(mutex); + return 0; + } + + // 初始化OGG上下文 + int32_t oggRet = AudioCapturerOggCtxInit(&g_aiVoiceContext->oggContext, + g_aiVoiceContext->inputChannelCount, + g_aiVoiceContext->inputSampleRate); + if (oggRet != 0) { + MEDIA_ERR_LOG("Failed to initialize OGG context"); + MediaMutexUnLock(mutex); + return -1; + } + + g_aiVoiceContext->oggEncapsulationStarted = true; + MEDIA_INFO_LOG("OGG encapsulation started successfully"); + + MediaMutexUnLock(mutex); + return 0; +} + +// 结束OGG封装 +int32_t AI_EndOggEncapsulation(void) +{ + MediaMutexHandle mutex = GetGlobalMutex(); + if (mutex == nullptr) { + MEDIA_ERR_LOG("Failed to get global mutex"); + return -1; + } + + MediaMutexLock(mutex); + if (g_aiVoiceContext == nullptr) { + MEDIA_ERR_LOG("AI voice context is null"); + MediaMutexUnLock(mutex); + return -1; + } + + if (!g_aiVoiceContext->useOggEncapsulation || !g_aiVoiceContext->oggEncapsulationStarted) { + MEDIA_ERR_LOG("OGG encapsulation not started %d %d",g_aiVoiceContext->useOggEncapsulation,g_aiVoiceContext->oggEncapsulationStarted); + MediaMutexUnLock(mutex); + return -1; + } + + // 完成OGG页面封装 + int32_t oggRet = AudioCaptureOggStreamFlush(&g_aiVoiceContext->oggContext, + g_aiVoiceContext->dataCallback, + g_aiVoiceContext->callbackUserData); + + MEDIA_INFO_LOG("OGG page completion result: ret=%d", oggRet); + + // 重新初始化OGG上下文以准备下一页 + AudioCapturerOggCtxDeInit(&g_aiVoiceContext->oggContext); + int32_t reinitRet = AudioCapturerOggCtxInit(&g_aiVoiceContext->oggContext, + g_aiVoiceContext->inputChannelCount, + g_aiVoiceContext->inputSampleRate); + if (reinitRet != 0) { + MEDIA_ERR_LOG("Failed to reinitialize OGG context"); + g_aiVoiceContext->oggEncapsulationStarted = false; + } + + MEDIA_INFO_LOG("OGG page completed and context reinitialized"); + + MediaMutexUnLock(mutex); + return 0; +} + // 全局互斥锁初始化函数(只创建一次,不释放) static bool InitGlobalMutex(void) { @@ -194,6 +571,12 @@ static void DeinitAIVoiceResources(AIVoiceContext *context) context->dataBuffer = nullptr; } + // 清理OGG上下文 + if (context->useOggEncapsulation) { + AudioCapturerOggCtxDeInit(&context->oggContext); + MEDIA_INFO_LOG("OGG context cleaned up"); + } + // 清理队列 if (context->inputQueue != NULL) { // 清空队列中的所有消息 @@ -245,12 +628,12 @@ static int32_t LocalAIVoiceStart(AIVoiceContext *context) // MEDIA_ERR_LOG("set modem out device connect failed"); // } - // create downlink AudioStreamIn + // create downlink AudioStreamIn (使用输入流参数) CapturerInputConfig captureConfig = {}; - captureConfig.audioFormat = context->audioFormat; - captureConfig.channelCount = context->channelCount; - captureConfig.linkDir = AUDIO_DOWN_LINK; - captureConfig.sampleRate = context->sampleRate; + captureConfig.audioFormat = context->inputAudioFormat; + captureConfig.channelCount = context->inputChannelCount; + captureConfig.linkDir = AUDIO_UP_LINK; + captureConfig.sampleRate = context->inputSampleRate; captureConfig.sessionID = context->sessionId; captureConfig.streamType = AUDIO_STREAM_VOICE_ASSISTANT; @@ -259,12 +642,12 @@ static int32_t LocalAIVoiceStart(AIVoiceContext *context) MEDIA_ERR_LOG("audio stream in init failed"); } - // create uplink AudioStreamOut + // create uplink AudioStreamOut (使用输出流参数) AudioRendererConfig renderConfig = {}; - renderConfig.audioFormat = context->audioFormat; - renderConfig.channelCount = context->channelCount; - renderConfig.linkDir = AUDIO_UP_LINK; - renderConfig.sampleRate = context->sampleRate; + renderConfig.audioFormat = context->outputAudioFormat; + renderConfig.channelCount = context->outputChannelCount; + renderConfig.linkDir = AUDIO_DOWN_LINK; + renderConfig.sampleRate = context->outputSampleRate; renderConfig.sessionID = context->sessionId; renderConfig.streamType = AUDIO_STREAM_VOICE_ASSISTANT; @@ -392,9 +775,30 @@ static void AIVoiceInputThreadFunc(void *data) int32_t ret = AudioStreamInObtainBuffer(context->dataBuffer, &readSize); if (ret == 0 && readSize > 0) { - // 调用音频数据回调函数 - if (context->dataCallback != nullptr) { - context->dataCallback(context->dataBuffer, readSize, context->callbackUserData); + // 如果使用OGG封装 + if (context->useOggEncapsulation) { + // 检查OGG封装是否已开始 + if (!context->oggEncapsulationStarted) { + // OGG封装未开始,跳过处理 + LOS_Msleep(10); // 10ms + continue; + } + + // 使用OGG封装处理数据 + int32_t oggRet = AudioCapturerWriteInOGG(&context->oggContext, context->dataBuffer, readSize, + context->dataCallback, context->callbackUserData, false); + if (oggRet != 0) { + MEDIA_ERR_LOG("OGG encapsulation failed: ret=%d", oggRet); + // 如果OGG封装失败,回退到原始数据 + if (context->dataCallback != nullptr) { + context->dataCallback(context->dataBuffer, readSize, context->callbackUserData); + } + } + } else { + // 直接调用音频数据回调函数 + if (context->dataCallback != nullptr) { + context->dataCallback(context->dataBuffer, readSize, context->callbackUserData); + } } // Write audio data to output stream (echo back) @@ -404,7 +808,8 @@ static void AIVoiceInputThreadFunc(void *data) // } } - usleep(10000); // 10ms + + LOS_Msleep(10); } @@ -634,7 +1039,9 @@ static void AIVoiceThread(void *priv) printf("AI voice thread cleanup completed\r\n"); } int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData, - const AIVoiceAudioParams* audioParams) + const AIVoiceAudioParams* inputAudioParams, + const AIVoiceAudioParams* outputAudioParams, + bool useOggEncapsulation) { MEDIA_ERR_LOG("StartAIVoice"); app_task_definition_t task_cfg; @@ -666,21 +1073,46 @@ int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData, g_aiVoiceContext->dataCallback = dataCallback; g_aiVoiceContext->callbackUserData = userData; - // 设置音频参数(如果未传入则使用默认值) - if (audioParams != nullptr) { - g_aiVoiceContext->audioFormat = (AudioCodecFormat)audioParams->audioFormat; - g_aiVoiceContext->channelCount = audioParams->channelCount; - g_aiVoiceContext->sampleRate = audioParams->sampleRate; - MEDIA_INFO_LOG("Using custom audio params: format=%d, channels=%u, sampleRate=%u", - audioParams->audioFormat, audioParams->channelCount, audioParams->sampleRate); + // 设置输入流音频参数(如果未传入则使用默认值) + if (inputAudioParams != nullptr) { + g_aiVoiceContext->inputAudioFormat = (AudioCodecFormat)inputAudioParams->audioFormat; + g_aiVoiceContext->inputChannelCount = inputAudioParams->channelCount; + g_aiVoiceContext->inputSampleRate = inputAudioParams->sampleRate; + MEDIA_INFO_LOG("Using custom input audio params: format=%d, channels=%u, sampleRate=%u", + inputAudioParams->audioFormat, inputAudioParams->channelCount, inputAudioParams->sampleRate); } else { - g_aiVoiceContext->audioFormat = DEFAULT_AUDIO_FORMAT; - g_aiVoiceContext->channelCount = DEFAULT_CHANNEL_COUNT; - g_aiVoiceContext->sampleRate = DEFAULT_SAMPLE_RATE; - MEDIA_INFO_LOG("Using default audio params: format=%d, channels=%u, sampleRate=%u", + g_aiVoiceContext->inputAudioFormat = DEFAULT_AUDIO_FORMAT; + g_aiVoiceContext->inputChannelCount = DEFAULT_CHANNEL_COUNT; + g_aiVoiceContext->inputSampleRate = DEFAULT_SAMPLE_RATE; + MEDIA_INFO_LOG("Using default input audio params: format=%d, channels=%u, sampleRate=%u", DEFAULT_AUDIO_FORMAT, DEFAULT_CHANNEL_COUNT, DEFAULT_SAMPLE_RATE); } + // 设置输出流音频参数(如果未传入则使用默认值) + if (outputAudioParams != nullptr) { + g_aiVoiceContext->outputAudioFormat = (AudioCodecFormat)outputAudioParams->audioFormat; + g_aiVoiceContext->outputChannelCount = outputAudioParams->channelCount; + g_aiVoiceContext->outputSampleRate = outputAudioParams->sampleRate; + MEDIA_INFO_LOG("Using custom output audio params: format=%d, channels=%u, sampleRate=%u", + outputAudioParams->audioFormat, outputAudioParams->channelCount, outputAudioParams->sampleRate); + } else { + g_aiVoiceContext->outputAudioFormat = DEFAULT_AUDIO_FORMAT; + g_aiVoiceContext->outputChannelCount = DEFAULT_CHANNEL_COUNT; + g_aiVoiceContext->outputSampleRate = DEFAULT_SAMPLE_RATE; + MEDIA_INFO_LOG("Using default output audio params: format=%d, channels=%u, sampleRate=%u", + DEFAULT_AUDIO_FORMAT, DEFAULT_CHANNEL_COUNT, DEFAULT_SAMPLE_RATE); + } + + // 设置OGG封装参数 + g_aiVoiceContext->useOggEncapsulation = useOggEncapsulation; + g_aiVoiceContext->oggEncapsulationStarted = false; + if (g_aiVoiceContext->useOggEncapsulation) { + MEDIA_INFO_LOG("OGG encapsulation enabled by parameter"); + // OGG上下文将在开始接口调用时初始化 + } else { + MEDIA_INFO_LOG("OGG encapsulation disabled by parameter"); + } + MediaMutexUnLock(mutex); MediaThreadJoin(&g_aiVoiceContext->taskHandle); @@ -851,16 +1283,43 @@ static void ExampleAIVoiceDataCallback(const uint8_t* data, uint32_t size, void* } // 使用示例: -// 1. 使用默认参数 -// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, nullptr); +// 1. 使用默认参数(输入流和输出流都使用默认参数,不使用OGG封装) +// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, nullptr, nullptr, false); // -// 2. 使用自定义参数 -// AIVoiceAudioParams audioParams = { +// 2. 只自定义输入流参数,输出流使用默认参数,不使用OGG封装 +// AIVoiceAudioParams inputParams = { // .audioFormat = MP3, // .channelCount = 1, // .sampleRate = 16000 // }; -// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, &audioParams); +// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, &inputParams, nullptr, false); +// +// 3. 分别自定义输入流和输出流参数,不使用OGG封装 +// AIVoiceAudioParams inputParams = { +// .audioFormat = PCM, +// .channelCount = 1, +// .sampleRate = 16000 +// }; +// AIVoiceAudioParams outputParams = { +// .audioFormat = MP3, +// .channelCount = 2, +// .sampleRate = 44100 +// }; +// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, &inputParams, &outputParams, false); +// +// 4. 使用OGG封装(需要手动控制开始和结束) +// AIVoiceAudioParams inputParams = { +// .audioFormat = OPUS, +// .channelCount = 1, +// .sampleRate = 16000 +// }; +// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, &inputParams, nullptr, true); +// // 开始OGG封装 +// AI_StartOggEncapsulation(); +// // 处理音频数据... +// // 结束OGG封装 +// AI_EndOggEncapsulation(); +// 注意:当useOggEncapsulation为true时,需要手动调用开始和结束接口来控制OGG封装 // // 队列使用示例: // uint8_t audioData[] = {0x01, 0x02, 0x03, 0x04}; diff --git a/code/sdk/zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp b/code/sdk/zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp index 132131882..fa464e2f0 100644 --- a/code/sdk/zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp +++ b/code/sdk/zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp @@ -339,16 +339,19 @@ int ai_service_start(void) printf("[AI] AI service started successfully\n"); // 创建与ai_generate_start_config一致的音频参数 - AIVoiceAudioParams audioParams = { + AIVoiceAudioParams audioParams_in = { .audioFormat = PCM, .channelCount = AI_DEFAULT_CHANNEL_COUNT, .sampleRate = AI_DEFAULT_SAMPLE_RATE }; - printf("[AI] Starting AI voice with audio params: format=%d, channels=%u, sampleRate=%u\n", - audioParams.audioFormat, audioParams.channelCount, audioParams.sampleRate); + AIVoiceAudioParams audioParams_out = { + .audioFormat = OPUS, + .channelCount = AI_DEFAULT_CHANNEL_COUNT, + .sampleRate = AI_DEFAULT_SAMPLE_RATE + }; - old_Start_AIVoice(AIVoiceDataCallback_, nullptr, &audioParams); + old_Start_AIVoice(AIVoiceDataCallback_, nullptr, &audioParams_in,&audioParams_out,false); return 0; } @@ -485,10 +488,15 @@ int ai_send_event(ai_event_type_t event_type) int ret = uapi_ws_send_text(json_string); if (ret == 0) { printf("[AI] Event '%s' sent successfully\n", ai_get_event_name(event_type)); - if(AI_EVENT_AUDIO_COMPLETED) + if(event_type==AI_EVENT_AUDIO_COMPLETED) { asr_is_start=0; +// AI_EndOggEncapsulation(); } +// if(event_type==AI_EVENT_AUDIO_CREATE) + // { + // AI_StartOggEncapsulation(); + // } } else { printf("[AI] Failed to send event '%s', ret: %d\n", ai_get_event_name(event_type), ret); } diff --git a/code/sdk/zhihui/ai_old/bk_websocket.h b/code/sdk/zhihui/ai_old/bk_websocket.h index e43b0fc74..569df2cd4 100644 --- a/code/sdk/zhihui/ai_old/bk_websocket.h +++ b/code/sdk/zhihui/ai_old/bk_websocket.h @@ -50,7 +50,7 @@ typedef struct { ws_message_queue_t *msg_queue; char host[128]; char path[128]; - char query[128]; + char query[300]; int port; int handshake; @@ -59,6 +59,14 @@ typedef struct { ws_parser_t* parser; int sockfd; /* socket文件描述符 */ + /* SSL/TLS 支持 */ + bool use_ssl; + void* ssl_context; /* mbedtls_ssl_context */ + void* ssl_config; /* mbedtls_ssl_config */ + void* ctr_drbg; /* mbedtls_ctr_drbg_context */ + void* entropy; /* mbedtls_entropy_context */ + void* cacert; /* mbedtls_x509_crt */ + /* 数据包回调函数 */ on_ws_packet_callback packet_callback; } WebSocketClient; @@ -79,6 +87,13 @@ void close_websocket_client(WebSocketClient *client) ; int receive_websocket_message(WebSocketClient *client, unsigned char *buf,int len,WebSocketMessageType *code, uint32_t timeout_ms); int send_websocket_message(WebSocketClient *client, uint8_t *message, size_t message_len, WebSocketMessageType type) ; +// SSL/TLS 支持函数 +int ws_ssl_init(WebSocketClient *client); +void ws_ssl_cleanup(WebSocketClient *client); +int ws_ssl_handshake(WebSocketClient *client); +int ws_ssl_send(WebSocketClient *client, const uint8_t *data, size_t len); +int ws_ssl_recv(WebSocketClient *client, uint8_t *data, size_t len); + // WebSocket测试客户端(当SUPPORT_WEBSOCKET_TEST定义时可用) #ifdef SUPPORT_WEBSOCKET_TEST extern WebSocketClient *test_client; diff --git a/code/sdk/zhihui/ai_old/websocket_server_example.cpp b/code/sdk/zhihui/ai_old/websocket_server_example.cpp index 00b709b6e..1f620a8bf 100644 --- a/code/sdk/zhihui/ai_old/websocket_server_example.cpp +++ b/code/sdk/zhihui/ai_old/websocket_server_example.cpp @@ -19,6 +19,9 @@ #include "chat_websocket_server.h" #include "tts_websocket_server.h" +#include "chat_https_servrt.h" + + // #include "common/typed_text.h" @@ -180,8 +183,22 @@ void handle_asr_data(const ws_event_data_t *event_data) // asr_is_ready=0; if(data.is_end) { - ai_chat_send_txt(0, data.result_text, g_chat_server->client,false); - if (g_ai_question_cb) g_ai_question_cb(data.result_text); else printf("[WS][ASR] ai_question 回调未注册,丢弃内容: %s\n", data.result_text); + #ifdef HTTP_TEST + + char buf[512]={0}; + int ret=http_posttest( data.result_text,buf,512); + + if(ret==0) + { + tts_is_ready = TTS_READY_SENT_SUCCESS; + g_tts_server->interrupt_flag=0; + + bool result = uapi_send_long_text_tts(g_tts_server->client, buf, + NULL, NULL, 0, 0, 0, NULL); + } + #endif + ai_chat_send_txt(0, data.result_text, g_chat_server->client,false); + if (g_ai_question_cb) g_ai_question_cb(data.result_text); else printf("[WS][ASR] ai_question 回调未注册,丢弃内容: %s\n", data.result_text); } break; @@ -210,8 +227,8 @@ void handle_tts_data(const ws_event_data_t *event_data) { if (event_data->msg_type == WS_BINARY) { printf("TTS binary data: %d bytes\n", event_data->data_len); - set_AIStreamend(0); AI_SendAudioDataToQueue(event_data->data,event_data->data_len); + } else if (event_data->msg_type == WS_TEXT) { printf("TTS text data: %s\n", (char *)event_data->data); tts_event_type_t event = tts_analysis((char *)event_data->data); @@ -545,7 +562,7 @@ errcode_t start_all_websocket_servers(void) } // ai_media_init(); - old_Start_AIVoice(AIVoiceDataCallback_,nullptr); + old_Start_AIVoice(AIVoiceDataCallback_,nullptr,nullptr,nullptr,false); /* 不进行默认回退注册,未注册时仅打印错误日志 */ diff --git a/code/sdk/zhihui/services/ble_service/blemoder/bleInstruct.cpp b/code/sdk/zhihui/services/ble_service/blemoder/bleInstruct.cpp index d71ed6470..0a187391f 100644 --- a/code/sdk/zhihui/services/ble_service/blemoder/bleInstruct.cpp +++ b/code/sdk/zhihui/services/ble_service/blemoder/bleInstruct.cpp @@ -182,12 +182,14 @@ bool ble_add_sos_number(uint8_t sex,char *name,char *number) return SAFE_CALL_BOOL(bleInstruct_t.ble_add_sos_number_cb, sex, name, number); } +#ifdef SUPPORT_ALIPAY_SEC +#include "alipay_feature.h" void ble_Alipayunbind(void) { - // const alipay_svr_api_t *alipay_api = alipay_svr_get_api(); - // alipay_api->remove_bind(); + const alipay_svr_api_t *alipay_api = alipay_svr_get_api(); + alipay_api->remove_bind(); } - +#endif void get_music_path(char *buf,uint8_t max_len) { -- 2.17.1