修改ai音频函数,使支持参数输入,及ogg封装
authorhcj <1224667075@qq.com>
Thu, 25 Sep 2025 13:29:58 +0000 (21:29 +0800)
committerhcj <1224667075@qq.com>
Thu, 25 Sep 2025 13:29:58 +0000 (21:29 +0800)
code/sdk/zhihui/ai/include/ai_media.h
code/sdk/zhihui/ai/media/CMakeLists.txt
code/sdk/zhihui/ai/media/ai_media_service_ext.cpp
code/sdk/zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp
code/sdk/zhihui/ai_old/bk_websocket.h
code/sdk/zhihui/ai_old/websocket_server_example.cpp
code/sdk/zhihui/services/ble_service/blemoder/bleInstruct.cpp

index 433d419..d070e43 100644 (file)
@@ -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 写入音频数据到输出流
index f93f445..878be8c 100644 (file)
@@ -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
index eb82115..455c1db 100644 (file)
@@ -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<unsigned char*>(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<unsigned char*>(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};
index 1321318..fa464e2 100644 (file)
@@ -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);
     }
index e43b0fc..569df2c 100644 (file)
@@ -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;
index 00b709b..1f620a8 100644 (file)
@@ -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);
     
     /* 不进行默认回退注册,未注册时仅打印错误日志 */
 
index d71ed64..0a18739 100644 (file)
@@ -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)
 {