From 2e7581363a22418cefc554fcd59965fac7008702 Mon Sep 17 00:00:00 2001 From: hcj <1224667075@qq.com> Date: Thu, 27 Nov 2025 18:21:44 +0800 Subject: [PATCH] =?utf8?q?=E4=BF=AE=E6=94=B9vad=E6=95=B0=E6=8D=AE=E8=BE=93?= =?utf8?q?=E5=85=A5=EF=BC=8C=E9=87=87=E7=94=A8=E7=9B=B4=E6=8E=A5dmp?= =?utf8?q?=E4=B8=AD=E7=9A=84=E6=80=95pcm=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../audio/source/core/include/core_module.h | 2 +- .../brandy/common/include/plugin_common.h | 3 + .../plugins/brandy/common/src/plugin_common.c | 102 +++-- .../brandy/primary/primary_sub_port_in.c | 36 +- .../tools/pkg/bin/brandy/dsp/max/dsp_main.bin | Bin 351688 -> 351800 bytes code/sdk/zhihui/ai/include/ai_media.h | 29 ++ .../zhihui/ai/media/ai_media_service_ext.cpp | 364 +++++++++++++++++- .../zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp | 2 +- .../ai_old/websocket_server_example.cpp | 8 +- code/sdk/zhihui/imsl/src/vad_server.c | 8 +- .../zhihui/leiniao/imsl_ln/leiniao_api_at.c | 2 +- .../zhihui/leiniao/imsl_ln/ln_api_websocket.c | 39 +- 12 files changed, 533 insertions(+), 62 deletions(-) diff --git a/code/sdk/drivers/drivers/driver/audio/source/core/include/core_module.h b/code/sdk/drivers/drivers/driver/audio/source/core/include/core_module.h index 2c52f429e..c7d99a5bc 100644 --- a/code/sdk/drivers/drivers/driver/audio/source/core/include/core_module.h +++ b/code/sdk/drivers/drivers/driver/audio/source/core/include/core_module.h @@ -31,7 +31,7 @@ extern "C" { #define MOD_AOP_MAX_NUM 2 #define MOD_TRACK_MAX_NUM 2 #define MOD_ADEC_MAX_NUM 2 -#define MOD_AENC_MAX_NUM 1 +#define MOD_AENC_MAX_NUM 2 #define MOD_ANC_MAX_NUM 0 #define MOD_ANPU_MAX_NUM 0 #define MOD_HAID_MAX_NUM 1 diff --git a/code/sdk/middleware/services/media/hal/audio/plugins/brandy/common/include/plugin_common.h b/code/sdk/middleware/services/media/hal/audio/plugins/brandy/common/include/plugin_common.h index 7afbe27ec..53d8460a9 100644 --- a/code/sdk/middleware/services/media/hal/audio/plugins/brandy/common/include/plugin_common.h +++ b/code/sdk/middleware/services/media/hal/audio/plugins/brandy/common/include/plugin_common.h @@ -34,6 +34,7 @@ extern "C" { typedef enum { AUDIO_MOD_AI = 0, + AUDIO_MOD_AI_SEA, AUDIO_MOD_ADP, AUDIO_MOD_ADEC, AUDIO_MOD_AENC, @@ -102,6 +103,8 @@ int32_t GetParamByValue(uint8_t *param, uint32_t paramLen, const char *value, uint32_t valueLen, uint32_t configLen); int32_t SetSeaEnable(td_handle seaHandle, const char *value, uint32_t valueLen); + +bool isMultipleSeaOpen(void); #endif int32_t ExtraParamsInvoke(AudioHandle portHandle, struct InvokeAttr *invokeAttr, diff --git a/code/sdk/middleware/services/media/hal/audio/plugins/brandy/common/src/plugin_common.c b/code/sdk/middleware/services/media/hal/audio/plugins/brandy/common/src/plugin_common.c index 0126bbcb0..16d43ef93 100644 --- a/code/sdk/middleware/services/media/hal/audio/plugins/brandy/common/src/plugin_common.c +++ b/code/sdk/middleware/services/media/hal/audio/plugins/brandy/common/src/plugin_common.c @@ -40,7 +40,9 @@ extern "C" { #define WAIT_CREATE_AENC_SUCCESS_US 10000 static bool g_audioSdkIsInited = false; -static bool g_isCanCreateAenc = true; +static int32_t g_isCreateAencCount = 0; +static int32_t g_subPortSeaOpenCount = 0; +td_handle g_seaHandle = 0; static MediaHalMutexHandle g_audioSdkInitLock = NULL; #ifdef SUPPORT_NV static audio_vqe_param_t g_vqeParam = { 0 }; @@ -425,8 +427,22 @@ static int32_t SetSeaAfeAttr(td_handle seaHandle) return MEDIA_HAL_OK; } +bool isMultipleSeaOpen(void) +{ + printf("%s %d\r\n","g_subPortSeaOpenCount",g_subPortSeaOpenCount); + + return g_subPortSeaOpenCount > 1; +} + int32_t AudioSDKSeaOpen(td_handle *seaHandle, struct PortPluginAttr inputAttr) { + MediaHalMutexLock(g_audioSdkInitLock); + if (g_subPortSeaOpenCount > 0) { + *seaHandle = g_seaHandle; + g_subPortSeaOpenCount++; + MediaHalMutexUnLock(g_audioSdkInitLock); + return MEDIA_HAL_OK; + } int32_t ret; uapi_sea_attr seaAttr = {}; uapi_sea_eng_sel seaEng = {}; @@ -462,23 +478,38 @@ int32_t AudioSDKSeaOpen(td_handle *seaHandle, struct PortPluginAttr inputAttr) if (ret != MEDIA_HAL_OK) { MEDIA_HAL_LOGW(MODULE_NAME, "set sea param failed"); } + g_seaHandle = *seaHandle; + g_subPortSeaOpenCount++; MEDIA_HAL_LOGD(MODULE_NAME, "success"); + printf("AudioSDKSeaOpen %s %d\r\n","g_subPortSeaOpenCount",g_subPortSeaOpenCount); + MediaHalMutexUnLock(g_audioSdkInitLock); return MEDIA_HAL_OK; SEA_UNLOAD_ENGINE: (void)uapi_sea_unload_engine(UAPI_SEA_LIB_SEE, "imedia_2mic"); + MediaHalMutexUnLock(g_audioSdkInitLock); return ret; } int32_t AudioSDKSeaClose(td_handle seaHandle) { + MediaHalMutexLock(g_audioSdkInitLock); int32_t ret; + g_subPortSeaOpenCount--; + if (g_subPortSeaOpenCount > 0) { + MediaHalMutexUnLock(g_audioSdkInitLock); + return MEDIA_HAL_OK; + } + printf("AudioSDKSeaClose %s %d\r\n","g_subPortSeaOpenCount",g_subPortSeaOpenCount); + MEDIA_HAL_DOFUNC_TIME(ret = uapi_sea_destroy(seaHandle)); CHK_FAILED_NO_RETURN(ret, MEDIA_HAL_OK, "destroy sea failed"); MEDIA_HAL_DOFUNC_TIME(ret = uapi_sea_unload_engine(UAPI_SEA_LIB_SEE, "imedia_2mic")); CHK_FAILED_NO_RETURN(ret, MEDIA_HAL_OK, "unload sea engine failed"); MEDIA_HAL_LOGD(MODULE_NAME, "success"); + g_seaHandle = 0; + MediaHalMutexUnLock(g_audioSdkInitLock); return MEDIA_HAL_OK; } @@ -487,8 +518,7 @@ static int32_t WaitCreateAnec(void) int32_t cnt = 0; while (cnt < WAIT_CREATE_AENC_LOOP_CNT) { MediaHalMutexLock(g_audioSdkInitLock); - if (g_isCanCreateAenc) { - g_isCanCreateAenc = false; + if (g_isCreateAencCount < 0x2) { MediaHalMutexUnLock(g_audioSdkInitLock); return MEDIA_HAL_OK; } @@ -508,10 +538,9 @@ int32_t AudioSDKAencOpen(td_handle *aencHandle, uapi_aenc_attr aencAttr) return ret; } MEDIA_HAL_DOFUNC_TIME(ret = uapi_aenc_create(aencHandle)); - if (ret != MEDIA_HAL_OK) { - MEDIA_HAL_LOGE(MODULE_NAME, "uapi_aenc_create failed"); - g_isCanCreateAenc = true; - return ret; + if (ret != MEDIA_HAL_OK) { + MEDIA_HAL_LOGE(MODULE_NAME, "uapi_aenc_create failed"); + return ret; } MEDIA_HAL_LOGI(MODULE_NAME, "codec_id: 0x%x", aencAttr.codec_id); @@ -520,22 +549,24 @@ int32_t AudioSDKAencOpen(td_handle *aencHandle, uapi_aenc_attr aencAttr) MEDIA_HAL_LOGE(MODULE_NAME, "uapi_aenc_set_attr failed: 0x%x, codec id: 0x%x", ret, aencAttr.codec_id); goto CLOSE; } -#ifndef MELODY_AUDIO_SUPPORT - if (aencAttr.codec_id == UAPI_ACODEC_ID_OPUS) { - uint32_t bitRate = OPUS_ENC_BIT_RATE; - ret = uapi_aenc_set_param(*aencHandle, UAPI_AENC_PARAM_BIT_RATE, &bitRate, sizeof(uint32_t)); - if (ret != MEDIA_HAL_OK) { - MEDIA_HAL_LOGE(MODULE_NAME, "uapi set aenc send cmd bit Rate faild, ret: 0x%x", ret); - goto CLOSE; - } - } +#ifndef MELODY_AUDIO_SUPPORT + if (aencAttr.codec_id == UAPI_ACODEC_ID_OPUS) { + uint32_t bitRate = OPUS_ENC_BIT_RATE; + ret = uapi_aenc_set_param(*aencHandle, UAPI_AENC_PARAM_BIT_RATE, &bitRate, sizeof(uint32_t)); + if (ret != MEDIA_HAL_OK) { + MEDIA_HAL_LOGE(MODULE_NAME, "uapi set aenc send cmd bit Rate faild, ret: 0x%x", ret); + goto CLOSE; + } + } #endif + MediaHalMutexLock(g_audioSdkInitLock); + g_isCreateAencCount++; + MediaHalMutexUnLock(g_audioSdkInitLock); MEDIA_HAL_LOGD(MODULE_NAME, "success"); return MEDIA_HAL_OK; CLOSE: MEDIA_HAL_DOFUNC_TIME((void)uapi_aenc_destroy(*aencHandle)); - g_isCanCreateAenc = true; return ret; } @@ -545,7 +576,7 @@ int32_t AudioSDKAencClose(td_handle aencHandle) MEDIA_HAL_DOFUNC_TIME(ret = uapi_aenc_destroy(aencHandle)); CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_aenc_destroy failed"); MediaHalMutexLock(g_audioSdkInitLock); - g_isCanCreateAenc = true; + g_isCreateAencCount--; MediaHalMutexUnLock(g_audioSdkInitLock); MEDIA_HAL_LOGD(MODULE_NAME, "success"); return MEDIA_HAL_OK; @@ -559,6 +590,13 @@ int32_t AudioSDKAttach(AudioSDKModuleType modType, td_handle srcHandle, td_handl MEDIA_HAL_DOFUNC_TIME(ret = uapi_ai_attach_output(srcHandle, dstHandle)); CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_ai_attach_output"); break; + case AUDIO_MOD_AI_SEA: + if (g_subPortSeaOpenCount <= 1) { // Sea only needs to be bound once. + printf("AudioSDKAttach %X %X\r\n",srcHandle,dstHandle); + MEDIA_HAL_DOFUNC_TIME(ret = uapi_ai_attach_output(srcHandle, dstHandle)); + CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_ai_attach_output"); + } + break; case AUDIO_MOD_ADP: MEDIA_HAL_DOFUNC_TIME(ret = uapi_adp_attach_output(srcHandle, dstHandle)); CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_adp_attach_output failed"); @@ -596,6 +634,14 @@ int32_t AudioSDKDetach(AudioSDKModuleType modType, td_handle srcHandle, td_handl MEDIA_HAL_DOFUNC_TIME(ret = uapi_ai_detach_output(srcHandle, dstHandle)); CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_ai_detach_output failed"); break; + case AUDIO_MOD_AI_SEA: + if (g_subPortSeaOpenCount <= 1) { // Sea only needs to be unbound once. + printf("AudioSDKDetach AUDIO_MOD_AI_SEA %X %X\r\n",srcHandle,dstHandle); + + MEDIA_HAL_DOFUNC_TIME(ret = uapi_ai_detach_output(srcHandle, dstHandle)); + CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_ai_detach_output failed"); + } + break; case AUDIO_MOD_ADP: MEDIA_HAL_DOFUNC_TIME(ret = uapi_adp_detach_output(srcHandle, dstHandle)); CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_adp_detach_output failed"); @@ -609,6 +655,8 @@ int32_t AudioSDKDetach(AudioSDKModuleType modType, td_handle srcHandle, td_handl CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_aenc_detach_output failed"); break; case AUDIO_MOD_SEA: + printf("AudioSDKDetach AUDIO_MOD_SEA %X %X\r\n",srcHandle,dstHandle); + MEDIA_HAL_DOFUNC_TIME(ret = uapi_sea_detach_output(srcHandle, UAPI_SEA_OUTPUT_ASR_SRC, dstHandle)); CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_sea_detach_output failed"); break; @@ -766,19 +814,19 @@ int32_t GetParamByValue(uint8_t *param, uint32_t paramLen, const char *value, uint32_t valueLen, uint32_t configLen) { uint32_t prefixlen = 0; - for (uint32_t i = 0; i < valueLen; i++) { - if (value[i] == ':') { - prefixlen = i + 1; - break; - } + for (uint32_t i = 0; i < valueLen; i++) { + if (value[i] == ':') { + prefixlen = i + 1; + break; + } } - if (prefixlen == 0) { + if (prefixlen == 0) { MEDIA_HAL_LOGE(MODULE_NAME, "prefix is not found."); - return MEDIA_HAL_ERR; + return MEDIA_HAL_ERR; } - if (memcpy_s(param, paramLen, value + prefixlen, configLen) != EOK) { + if (memcpy_s(param, paramLen, value + prefixlen, configLen) != EOK) { MEDIA_HAL_LOGE(MODULE_NAME, "set spk cali coef memcpy_s failed!"); - return MEDIA_HAL_ERR; + return MEDIA_HAL_ERR; } return MEDIA_HAL_OK; } diff --git a/code/sdk/middleware/services/media/hal/audio/plugins/brandy/primary/primary_sub_port_in.c b/code/sdk/middleware/services/media/hal/audio/plugins/brandy/primary/primary_sub_port_in.c index da2a6614e..1ca8cf7d0 100644 --- a/code/sdk/middleware/services/media/hal/audio/plugins/brandy/primary/primary_sub_port_in.c +++ b/code/sdk/middleware/services/media/hal/audio/plugins/brandy/primary/primary_sub_port_in.c @@ -71,7 +71,7 @@ typedef struct { /* attr context */ bool isRaw; struct PortPluginAttr inputAttr; -} SubPortTrackInContext; + } SubPortTrackInContext; typedef struct { #ifdef AUDIO_SDK_SUPPORT @@ -80,7 +80,7 @@ typedef struct { td_handle reserved; } SubPortInContext; -static bool g_subPortTrackInOpened = false; +static uint32_t g_subPortTrackInOpened = 0; static MediaHalMutexHandle g_subPortTrackInLock = NULL; typedef struct { @@ -154,7 +154,7 @@ static int32_t SubPortTrackInInit(SubPortTrackInContext *portTrackInCtx, const s portTrackInCtx->common.SetVolume = AudioPrimarySubPortInSetVolume; portTrackInCtx->common.GetVolume = AudioPrimarySubPortInGetVolume; portTrackInCtx->common.Invoke = AudioPrimarySubPortInInvoke; - MEDIA_HAL_LOGD(MODULE_NAME, "success"); + MEDIA_HAL_LOGD(MODULE_NAME, "success"); return MEDIA_HAL_OK; } @@ -163,7 +163,7 @@ static int32_t AIDevOpen(td_handle *aiHandle, enum AudioCategory audioCategory) { MediaHalInitStaticMutexLock(&g_subPortTrackInLock); MediaHalMutexLock(g_subPortTrackInLock); - if (!g_subPortTrackInOpened) { + if (g_subPortTrackInOpened == 0) { int32_t ret; uapi_ai_attr aiAttr = {}; uapi_ai_port aiPort; @@ -199,7 +199,7 @@ static int32_t AIDevOpen(td_handle *aiHandle, enum AudioCategory audioCategory) MediaHalMutexUnLock(g_subPortTrackInLock); return ret; } -uapi_ai_gain gain = {}; + uapi_ai_gain gain = {}; MEDIA_HAL_DOFUNC_TIME(ret = uapi_ai_get_volume(*aiHandle, &gain)); CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_ai_get_volume failed"); #ifdef BOARD_FD230 @@ -207,13 +207,12 @@ uapi_ai_gain gain = {}; #elif defined(BORAD_N9) || defined(BORAD_W9) gain.integer = gain.integer + 10; // 前端mic灵敏度不同可通过此处调增,增加4db增益 默认gain.integer = gain.integer+4 #elif defined(LEINIAO) - gain.integer = gain.integer + 8; // 前端mic灵敏度不同可通过此处调增,增加4db增益 默认gain.integer = gain.integer+4 + gain.integer = gain.integer +3; // 前端mic灵敏度不同可通过此处调增,增加4db增益 默认gain.integer = gain.integer+4 #endif MEDIA_HAL_DOFUNC_TIME(ret = uapi_ai_set_volume(*aiHandle, &gain)); - CHK_FAILED_RETURN(ret, MEDIA_HAL_OK, ret, "uapi_ai_set_volume failed"); - - g_subPortTrackInOpened = true; + CHK_FAILED_NO_RETURN(ret, MEDIA_HAL_OK, "uapi_ai_set_volume failed"); } + g_subPortTrackInOpened++; MediaHalMutexUnLock(g_subPortTrackInLock); MEDIA_HAL_LOGD(MODULE_NAME, "success"); @@ -226,7 +225,8 @@ static int32_t AIDevClose(td_handle aiHandle) { MediaHalInitStaticMutexLock(&g_subPortTrackInLock); MediaHalMutexLock(g_subPortTrackInLock); - if (g_subPortTrackInOpened) { + g_subPortTrackInOpened--; + if (g_subPortTrackInOpened == 0) { int32_t ret; MEDIA_HAL_DOFUNC_TIME(ret = uapi_ai_close(aiHandle)); if (ret != MEDIA_HAL_OK) { @@ -234,8 +234,6 @@ static int32_t AIDevClose(td_handle aiHandle) MediaHalMutexUnLock(g_subPortTrackInLock); return ret; } - - g_subPortTrackInOpened = false; } MediaHalMutexUnLock(g_subPortTrackInLock); @@ -269,7 +267,7 @@ static int32_t AudioSDKOpenInternal(SubPortTrackInContext *portTrackInCtx) aencAttr.param.interleaved = true; if (inputAttrTemp.type == AUDIO_IN_MEDIA) { aencAttr.frame_mode_output = TD_TRUE; - } + } aencAttr.param.channels = inputAttrTemp.channelCount; aencAttr.param.bit_depth = inputAttrTemp.bitWidth; aencAttr.param.sample_rate = inputAttrTemp.sampleRate; @@ -529,13 +527,13 @@ static int32_t CheckParam(SubPortTrackInContext *portTrackInCtx, uint8_t *buffer MEDIA_HAL_LOGE(MODULE_NAME, "input buffer too small, actual: %llu vs except: %u", requestBytes, audioStream.size); return MEDIA_HAL_INVALID_PARAM; - } - + } + if (requestBytes > SIZE_MAX) { MEDIA_HAL_LOGE(MODULE_NAME, "input buffer size overflow"); return MEDIA_HAL_INVALID_PARAM; } - + if (memcpy_s(buffer, (size_t)requestBytes, audioStream.data, audioStream.size) != EOK) { MEDIA_HAL_LOGE(MODULE_NAME, "memcpy_s failed"); return MEDIA_HAL_ERR; @@ -569,11 +567,11 @@ int32_t AudioPrimarySubPortInAcquireFrame(AudioHandle trackHandle, uint8_t *buff *replyBytes = audioStream.size; ret = CheckParam(portTrackInCtx, buffer, requestBytes, audioStream); if (ret != MEDIA_HAL_OK) { - MEDIA_HAL_LOGE(MODULE_NAME, "param is invalid"); + MEDIA_HAL_LOGE(MODULE_NAME, "param is invalid"); (void)uapi_adp_release_stream(portTrackInCtx->outputAdpHandle, &audioStream); return ret; } - + ret = uapi_adp_release_stream(portTrackInCtx->outputAdpHandle, &audioStream); if (ret != MEDIA_HAL_OK) { MEDIA_HAL_LOGE(MODULE_NAME, "release stream failed: 0x%x", ret); @@ -898,7 +896,7 @@ FREE_TRACK_IN: int32_t AudioPrimarySubPortInDestroyTrack(AudioHandle portHandle, AudioHandle trackHandle) { #ifdef AUDIO_SDK_SUPPORT - SubPortTrackInContext *portTrackInCtx = (SubPortTrackInContext *)trackHandle; + SubPortTrackInContext *portTrackInCtx = (SubPortTrackInContext *)trackHandle; int32_t ret = AIDetach(portTrackInCtx); CHK_FAILED_NO_RETURN(ret, MEDIA_HAL_OK, "AIDetach failed"); diff --git a/code/sdk/tools/pkg/bin/brandy/dsp/max/dsp_main.bin b/code/sdk/tools/pkg/bin/brandy/dsp/max/dsp_main.bin index 492ce9e98be0bc2f58877ad671c2273d02dbcf0a..5c93e91476590d4184c39673c784b8b186e641fc 100644 GIT binary patch delta 33202 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%0AT9=aXReB=XGX;mxVdTyfoI{^tW=w^C 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 delta 33044 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 secondStreamIn; // 第二个上行流句柄 + AudioCodecFormat secondInputAudioFormat; // 第二个输入音频格式 + uint32_t secondInputChannelCount; // 第二个输入声道数 + uint32_t secondInputSampleRate; // 第二个输入采样率 + bool useSecondStreamIn; // 是否使用第二个上行流 } AIVoiceContext; static MediaMutexHandle g_aiVoiceContextMutex = nullptr; @@ -720,6 +727,161 @@ static MediaMutexHandle GetGlobalMutex(void) static std::shared_ptr g_streamIn = nullptr; static std::shared_ptr g_streamOut = nullptr; +/** + * @brief 初始化第二个音频上行流 + * @param context AI语音上下文 + * @return 0 成功,-1 失败 + */ +static int32_t InitSecondAudioStreamIn(AIVoiceContext *context) +{ + if (context == nullptr) { + MEDIA_ERR_LOG("AI voice context is null"); + return -1; + } + + if (!context->useSecondStreamIn) { + MEDIA_INFO_LOG("Second audio stream in not enabled"); + return 0; + } + + // 创建第二个上行流实例 + context->secondStreamIn = std::make_shared(); + if (context->secondStreamIn == nullptr || context->secondStreamIn.get() == nullptr) { + MEDIA_ERR_LOG("Failed to create second audio stream in"); + return -1; + } + + // 配置第二个上行流参数 + CapturerInputConfig secondCaptureConfig = {}; + secondCaptureConfig.audioFormat = context->secondInputAudioFormat; + secondCaptureConfig.channelCount = context->secondInputChannelCount; + secondCaptureConfig.linkDir = AUDIO_UP_LINK; + secondCaptureConfig.sampleRate = context->secondInputSampleRate; + secondCaptureConfig.sessionID = context->sessionId; + secondCaptureConfig.streamType = AUDIO_STREAM_VOICE_ASSISTANT; + + // 初始化第二个上行流 + int32_t ret = context->secondStreamIn->Init(secondCaptureConfig); + if (ret != 0) { + MEDIA_ERR_LOG("Second audio stream in init failed: 0x%x", ret); + context->secondStreamIn.reset(); + context->secondStreamIn = nullptr; + return -1; + } + + MEDIA_INFO_LOG("Second audio stream in initialized successfully"); + return 0; +} + +/** + * @brief 启动第二个音频上行流 + * @param context AI语音上下文 + * @return 0 成功,-1 失败 + */ +static int32_t StartSecondAudioStreamIn(AIVoiceContext *context) +{ + if (context == nullptr || context->secondStreamIn == nullptr) { + return 0; // 如果未启用或未初始化,直接返回成功 + } + + int32_t ret = context->secondStreamIn->Start(); + if (ret != 0) { + MEDIA_ERR_LOG("Second audio stream in start failed: 0x%x", ret); + return -1; + } + + MEDIA_INFO_LOG("Second audio stream in started successfully"); + return 0; +} + +/** + * @brief 停止第二个音频上行流 + * @param context AI语音上下文 + * @return 0 成功,-1 失败 + */ +static int32_t StopSecondAudioStreamIn(AIVoiceContext *context) +{ + if (context == nullptr || context->secondStreamIn == nullptr) { + return 0; // 如果未启用或未初始化,直接返回成功 + } + + int32_t ret = context->secondStreamIn->Stop(); + if (ret != 0) { + MEDIA_ERR_LOG("Second audio stream in stop failed: 0x%x", ret); + return -1; + } + + MEDIA_INFO_LOG("Second audio stream in stopped successfully"); + return 0; +} + +/** + * @brief 反初始化第二个音频上行流 + * @param context AI语音上下文 + * @return 0 成功,-1 失败 + */ +static int32_t DeinitSecondAudioStreamIn(AIVoiceContext *context) +{ + if (context == nullptr || context->secondStreamIn == nullptr) { + return 0; // 如果未启用或未初始化,直接返回成功 + } + + context->secondStreamIn->Release(); + context->secondStreamIn.reset(); + context->secondStreamIn = nullptr; + + MEDIA_INFO_LOG("Second audio stream in deinitialized successfully"); + return 0; +} + +/** + * @brief 停止第二个音频上行流(线程安全版本,使用全局上下文) + * @return 0 成功,-1 失败 + */ +static int32_t StopSecondAudioStreamInSafe(void) +{ + int32_t ret = 0; + MediaMutexHandle mutex = GetGlobalMutex(); + if (mutex == nullptr) { + return -1; + } + + MediaMutexLock(mutex); + if (g_aiVoiceContext != nullptr) { + ret = StopSecondAudioStreamIn(g_aiVoiceContext); + if (ret != 0) { + MEDIA_ERR_LOG("second audio stream in stop failed"); + } + } + MediaMutexUnLock(mutex); + + return ret; +} + +/** + * @brief 反初始化第二个音频上行流(线程安全版本,使用全局上下文) + * @return 0 成功,-1 失败 + */ +static int32_t DeinitSecondAudioStreamInSafe(void) +{ + int32_t ret = 0; + MediaMutexHandle mutex = GetGlobalMutex(); + if (mutex == nullptr) { + return -1; + } + + MediaMutexLock(mutex); + if (g_aiVoiceContext != nullptr) { + ret = DeinitSecondAudioStreamIn(g_aiVoiceContext); + if (ret != 0) { + MEDIA_ERR_LOG("second audio stream in deinit failed"); + } + } + MediaMutexUnLock(mutex); + + return ret; +} + // 注意:g_aiVoiceContext 已在上方定义,这里不需要重复定义 @@ -795,6 +957,11 @@ static void DeinitAIVoiceResources(AIVoiceContext *context) MEDIA_INFO_LOG("OGG context cleaned up"); } + // 清理第二个上行流 + if (context->secondStreamIn != nullptr) { + DeinitSecondAudioStreamIn(context); + } + // 清理队列 if (context->inputQueue != NULL) { // 清空队列中的所有消息 @@ -879,6 +1046,22 @@ static int32_t LocalAIVoiceStart(AIVoiceContext *context) MEDIA_ERR_LOG("audio stream in start failed"); } + // 初始化第二个上行流(如果启用) + if (context->useSecondStreamIn) { + ret = InitSecondAudioStreamIn(context); + if (ret != 0) { + MEDIA_ERR_LOG("Failed to init second audio stream in"); + // 不返回错误,继续执行 + } else { + // 启动第二个上行流(如果初始化成功) + ret = StartSecondAudioStreamIn(context); + if (ret != 0) { + MEDIA_ERR_LOG("Failed to start second audio stream in"); + // 不返回错误,继续执行 + } + } + } + ret = AudioStreamOutPlay(); if (ret != 0) { MEDIA_ERR_LOG("audio stream out play failed"); @@ -900,15 +1083,26 @@ static int32_t LocalAIVoiceStop(void) if (ret != 0) { MEDIA_ERR_LOG("audio stream in stop failed"); } + ret = AudioStreamInDeinit(); + if (ret != 0) { + MEDIA_ERR_LOG("audio stream in release failed"); + } - ret = AudioStreamOutStop(); + // 停止第二个上行流 + ret = StopSecondAudioStreamInSafe(); if (ret != 0) { - MEDIA_ERR_LOG("audio stream out stop failed"); + MEDIA_ERR_LOG("second audio stream in stop failed"); } - ret = AudioStreamInDeinit(); + // 反初始化第二个上行流 + ret = DeinitSecondAudioStreamInSafe(); if (ret != 0) { - MEDIA_ERR_LOG("audio stream in release failed"); + MEDIA_ERR_LOG("second audio stream in deinit failed"); + } + + ret = AudioStreamOutStop(); + if (ret != 0) { + MEDIA_ERR_LOG("audio stream out stop failed"); } ret = AudioStreamOutDeinit(); @@ -1284,6 +1478,7 @@ static void AIVoiceThread(void *priv) int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData, const AIVoiceAudioParams* inputAudioParams, const AIVoiceAudioParams* outputAudioParams, + const AIVoiceAudioParams* secondStreamInParams, bool useOggEncapsulation) { MEDIA_ERR_LOG("StartAIVoice"); @@ -1356,6 +1551,24 @@ int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData, MEDIA_INFO_LOG("OGG encapsulation disabled by parameter"); } + // 设置第二个上行流参数(如果传入参数则启用,否则不启用) + if (secondStreamInParams != nullptr) { + g_aiVoiceContext->useSecondStreamIn = true; + g_aiVoiceContext->secondInputAudioFormat = (AudioCodecFormat)secondStreamInParams->audioFormat; + g_aiVoiceContext->secondInputChannelCount = secondStreamInParams->channelCount; + g_aiVoiceContext->secondInputSampleRate = secondStreamInParams->sampleRate; + g_aiVoiceContext->secondStreamIn = nullptr; + MEDIA_INFO_LOG("Second stream in enabled with params: format=%d, channels=%u, sampleRate=%u", + secondStreamInParams->audioFormat, secondStreamInParams->channelCount, secondStreamInParams->sampleRate); + } else { + g_aiVoiceContext->useSecondStreamIn = false; + g_aiVoiceContext->secondInputAudioFormat = DEFAULT_AUDIO_FORMAT; + g_aiVoiceContext->secondInputChannelCount = DEFAULT_CHANNEL_COUNT; + g_aiVoiceContext->secondInputSampleRate = DEFAULT_SAMPLE_RATE; + g_aiVoiceContext->secondStreamIn = nullptr; + MEDIA_INFO_LOG("Second stream in disabled (null parameter)"); + } + MediaMutexUnLock(mutex); MediaThreadJoin(&g_aiVoiceContext->taskHandle); @@ -1990,6 +2203,149 @@ int32_t AI_ForceInterrupt(void) return 0; } +/** + * @brief 设置第二个音频上行流参数 + * @param audioParams 第二个上行流音频参数,如果为nullptr则禁用第二个上行流 + * @return 0 成功,-1 失败 + */ +int32_t AI_SetSecondStreamInParams(const AIVoiceAudioParams* audioParams) +{ + 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, please start AI voice first"); + MediaMutexUnLock(mutex); + return -1; + } + + if (audioParams == nullptr) { + // 禁用第二个上行流 + g_aiVoiceContext->useSecondStreamIn = false; + MEDIA_INFO_LOG("Second audio stream in disabled"); + } else { + // 设置第二个上行流参数 + g_aiVoiceContext->useSecondStreamIn = true; + g_aiVoiceContext->secondInputAudioFormat = (AudioCodecFormat)audioParams->audioFormat; + g_aiVoiceContext->secondInputChannelCount = audioParams->channelCount; + g_aiVoiceContext->secondInputSampleRate = audioParams->sampleRate; + MEDIA_INFO_LOG("Second audio stream in params set: format=%d, channels=%u, sampleRate=%u", + audioParams->audioFormat, audioParams->channelCount, audioParams->sampleRate); + } + + MediaMutexUnLock(mutex); + return 0; +} + +/** + * @brief 获取第二个音频上行流参数 + * @param audioParams 输出参数:第二个上行流音频参数 + * @return 0 成功,-1 失败 + */ +int32_t AI_GetSecondStreamInParams(AIVoiceAudioParams* audioParams) +{ + if (audioParams == nullptr) { + MEDIA_ERR_LOG("Invalid output parameter"); + return -1; + } + + 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, please start AI voice first"); + MediaMutexUnLock(mutex); + return -1; + } + + audioParams->audioFormat = (int32_t)g_aiVoiceContext->secondInputAudioFormat; + audioParams->channelCount = g_aiVoiceContext->secondInputChannelCount; + audioParams->sampleRate = g_aiVoiceContext->secondInputSampleRate; + + MediaMutexUnLock(mutex); + return 0; +} + +/** + * @brief 检查第二个音频上行流是否启用 + * @return true 已启用,false 未启用或服务未运行 + */ +bool AI_IsSecondStreamInEnabled(void) +{ + bool enabled = false; + + MediaMutexHandle mutex = GetGlobalMutex(); + if (mutex == nullptr) { + return false; + } + + MediaMutexLock(mutex); + if (g_aiVoiceContext != nullptr) { + enabled = g_aiVoiceContext->useSecondStreamIn; + } + MediaMutexUnLock(mutex); + + return enabled; +} + +/** + * @brief 从第二个音频上行流获取音频数据缓冲区 + * @param data 音频数据缓冲区指针 + * @param size 输入参数:缓冲区大小,输出参数:实际读取的数据大小 + * @return 0 成功,-1 失败 + */ +int32_t AI_SecondStreamInObtainBuffer(uint8_t *data, uint32_t *size) +{ + if (data == nullptr || size == nullptr || *size == 0) { + MEDIA_ERR_LOG("Invalid parameters for second stream in obtain buffer"); + return -1; + } + + // 先获取流指针(需要锁保护) + std::shared_ptr secondStreamIn = nullptr; + MediaMutexHandle mutex = GetGlobalMutex(); + if (mutex != nullptr) { + MediaMutexLock(mutex); + if (g_aiVoiceContext != nullptr && + g_aiVoiceContext->useSecondStreamIn && + g_aiVoiceContext->secondStreamIn != nullptr) { + secondStreamIn = g_aiVoiceContext->secondStreamIn; + } + MediaMutexUnLock(mutex); + } + + if (secondStreamIn == nullptr) { + MEDIA_ERR_LOG("Second audio stream in is not available"); + return -1; + } + + // 读取数据(不需要锁) + CapturerBuffer buffer; + buffer.data = data; + buffer.size = *size; + int32_t ret = secondStreamIn->ObtainBuffer(buffer, false); + + if (ret < 0) { + MEDIA_ERR_LOG("Second stream in obtain buffer failed: %d", ret); + return -1; + } + else + { + // MEDIA_INFO_LOG("Second stream in obtain buffer %d\r\n",buffer.size); + } + + *size = static_cast(ret); + return 0; +} + #ifdef __cplusplus }; #endif 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 a462d56f3..64b50e16d 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 @@ -352,7 +352,7 @@ int ai_service_start(void) .sampleRate = AI_DEFAULT_SAMPLE_RATE }; - old_Start_AIVoice(AIVoiceDataCallback_V25,nullptr, nullptr,nullptr,false); + old_Start_AIVoice(AIVoiceDataCallback_V25,nullptr, nullptr,nullptr,nullptr,false); return 0; } diff --git a/code/sdk/zhihui/ai_old/websocket_server_example.cpp b/code/sdk/zhihui/ai_old/websocket_server_example.cpp index 4e6ebf2de..40cbbe3ff 100644 --- a/code/sdk/zhihui/ai_old/websocket_server_example.cpp +++ b/code/sdk/zhihui/ai_old/websocket_server_example.cpp @@ -482,7 +482,11 @@ bool chat_url_callback(ws_server_type_t type, char *url_buf, uint32_t url_size, snprintf(url_buf, url_size, "ws://118.145.158.207:22080/chat/ws?platform=moonshot"); } else if (g_chat_ai_type == 5) { // AI_TYPE_DE snprintf(url_buf, url_size, "ws://118.145.158.207:22080/chat/ws?platform=huoshan&model=ep-20250205103849-582m7"); - } else { + } + else if (g_chat_ai_type == 6) { // AI_TYPE_DE + snprintf(url_buf, url_size, "ws://118.145.158.207:22080/chat/ws?platform=telechat"); + } + else { snprintf(url_buf, url_size, "ws://118.145.158.207:22080/chat/ws?platform=aliyun"); } } @@ -612,7 +616,7 @@ errcode_t start_all_websocket_servers(uint8_t chat_ai_type) } // ai_media_init(); - old_Start_AIVoice(AIVoiceDataCallback_,nullptr,nullptr,nullptr,false); + old_Start_AIVoice(AIVoiceDataCallback_,nullptr,nullptr,nullptr,nullptr,false); /* 不进行默认回退注册,未注册时仅打印错误日志 */ diff --git a/code/sdk/zhihui/imsl/src/vad_server.c b/code/sdk/zhihui/imsl/src/vad_server.c index 8fd27ed75..b333bc584 100644 --- a/code/sdk/zhihui/imsl/src/vad_server.c +++ b/code/sdk/zhihui/imsl/src/vad_server.c @@ -128,6 +128,8 @@ static void vad_mp3_pcm_callback(const int16_t *pcm_data, int samples, int chann #endif static uint8_t dmp_auth=0; +uint32_t dmp_auth_max_count=10; + /** * @brief 处理 PCM 数据,直接送给 VAD(简化版) * @note VAD 库支持可变长度输入(推荐 < 100ms),无需固定切分 @@ -141,8 +143,8 @@ static errcode_t vad_process_pcm_data(const int16_t *pcm_data, size_t sample_cou } pcm_call_count++; - - LOGIN_SUCC: + +LOGIN_SUCC: if(dmp_auth==0) { @@ -421,7 +423,7 @@ errcode_t vad_server_init(vad_status_callback_t callback) struct VadConfig vad_config = { .vad_log_level = 1, /* INFO 日志 */ .sil_threshold = 63, /* 静音阈值 */ - .energy_threshold = 80000, /* 能量阈值 */ + .energy_threshold = 50000, /* 能量阈值 */ .min_speech_length = 240, /* 最短语音长度 (ms) */ .silence_detect_length = 300, /* 静音检测长度 (ms) */ .rule1_min_tailing_silence = 480, /* 规则1静音时间 (ms) */ diff --git a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c index 4fb245df0..da38acf41 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c @@ -256,7 +256,7 @@ static uint32_t ln_ws_start_at(uint8_t *para, uint32_t paraLen, char *strCommonC LN_UNUSED(paraLen); LN_UNUSED(strCommonCmd); printf("[LN][WS_START]\n"); - int ret = ln_ws_start(NULL,2000); + int ret = ln_ws_start(NULL,1500); if (ret != 0) { printf("[LN][WS_START] failed: %d\n", ret); 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 4c305f392..b1261cd24 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c @@ -701,7 +701,7 @@ int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type) LN_WS_WARN("Audio chat already active: %s\n", g_audio_chat_ctx.chat_id); return 0; } - ln_ws_send_voice_chat_interrupt("新对话开始"); + ln_ws_send_voice_chat_interrupt("新对话开始"); AI_ForceInterrupt(); @@ -782,17 +782,39 @@ static void AIVoiceDataCallback_ln(const uint8_t* data, uint32_t size, void* use // 更上层、更清晰、天然只处理录音数据 #if ENABLE_VAD_SERVER if (g_vad_server_enabled && g_vad_server_initialized && vad_server_is_running()) { + + #ifdef VAD_LOCAL_PM vad_audio_format_t format = VAD_AUDIO_FORMAT_MP3; // 默认 MP3 +errcode_t ret = vad_server_send_audio(format, data, size, false); + + #else + vad_audio_format_t format = VAD_AUDIO_FORMAT_PCM; // 默认 MP3 + if (data && size > 0) { - errcode_t ret = vad_server_send_audio(format, data, size, false); + + uint8_t *pcm_data=(uint8_t *) malloc(1024*3); + uint32_t len=1024*3; + + errcode_t ret= AI_SecondStreamInObtainBuffer(pcm_data,&len); + + if(ret==0 && len>0) + { + ret = vad_server_send_audio(format, pcm_data, len, false); +// AI_SendAudioDataToQueue(pcm_data, len); + if (ret != ERRCODE_SUCC) { /* 静默失败,避免过多日志 */ } else { // LN_WS_DEBUG("VAD: Sent %u bytes (format=%d, is_last=%d)\n", // size, format, false); } + } + free(pcm_data); + + } + #endif // 如果是结束标志,打印日志 if (false) { @@ -1141,6 +1163,12 @@ void ln_ws_audio_init(void) .channelCount = 1, .sampleRate = 16000 }; + + AIVoiceAudioParams audioParams_in_vad = { + .audioFormat = PCM, + .channelCount = 1, + .sampleRate = 16000 + }; AIVoiceAudioParams audioParams_out = { .audioFormat = MP3, @@ -1155,7 +1183,7 @@ void ln_ws_audio_init(void) #endif uapi_cat1_add_cat1_vote(); - old_Start_AIVoice(AIVoiceDataCallback_ln,NULL,&audioParams_in,&audioParams_out,false); + old_Start_AIVoice(AIVoiceDataCallback_ln,NULL,&audioParams_in,&audioParams_out,&audioParams_in_vad,false); // 设置初始化标志位 g_audio_initialized = true; @@ -1213,6 +1241,9 @@ void ln_ws_audio_deinit(void) #endif uapi_cat1_remove_cat1_vote(); + g_audio_playback_active = false; + g_audio_chat_ctx.finish_state = LN_CHAT_NOT_ENDED; + // 清除初始化标志位 g_audio_initialized = false; @@ -1562,7 +1593,7 @@ int ln_ws_send_voice_chat_interrupt(const char *reason) if (ret == 0) { LN_WS_DEBUG("VoiceChatInterrupt sent successfully\n"); - if (need_reconnect || g_audio_chat_ctx.finish_state == LN_CHAT_SENT_END_NO_REPLY) { + if (need_reconnect || (g_audio_chat_ctx.finish_state == LN_CHAT_SENT_END_NO_REPLY)) { // 如果检测到频繁数据接收,直接重连 LN_WS_INFO("Triggering reconnection due to frequent binary data reception\n"); if (ln_ws_reconnect() != 0) { -- 2.17.1