+19
@@ -0,0 +1,19 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef VOLC_CONV_SERVICE_H__
|
||||
#define VOLC_CONV_SERVICE_H__
|
||||
|
||||
#include "volc_conv_ai.h"
|
||||
typedef struct {
|
||||
volc_event_handler_t volc_event_handler;
|
||||
volc_engine_t engine;
|
||||
int wait_time;
|
||||
} volc_conv_service_t;
|
||||
|
||||
void conv_ai_service_task(void *pvParameters);
|
||||
void conv_ai_service_task_stop();
|
||||
void conv_ai_service_init();
|
||||
volc_conv_service_t get_conv_ai_service();
|
||||
|
||||
#endif /* VOLC_CONV_SERVICE_H__ */
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef VOLC_CONV_SERVICE_MANAGER_H__
|
||||
#define VOLC_CONV_SERVICE_MANAGER_H__
|
||||
|
||||
#include <stdio.h>
|
||||
#include "aios.h"
|
||||
|
||||
void volc_conv_service_manager_init(void);
|
||||
|
||||
#endif // VOLC_CONV_SERVICE_MANAGER_H__
|
||||
+523
@@ -0,0 +1,523 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "aios.h"
|
||||
#include "cJSON.h"
|
||||
#include "volc_hal.h"
|
||||
#include "volc_osal.h"
|
||||
#include "util/volc_log.h"
|
||||
#include "volc_service_common.h"
|
||||
#include "volc_conv.h"
|
||||
#include "volc_hal.h"
|
||||
#include "volc_hal_capture.h"
|
||||
#include "volc_hal_display.h"
|
||||
#include "volc_hal_player.h"
|
||||
// #include "volc_hal_file.h"
|
||||
|
||||
#include "volc_local_function_list.h"
|
||||
#include "mmap_generate_eaf.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
extern int8_t get_rssi(void);
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CONV_AI_CONFIG_FORMAT "{\
|
||||
\"ver\": 1,\
|
||||
\"iot\": {\
|
||||
\"instance_id\": \"%s\",\
|
||||
\"product_key\": \"%s\",\
|
||||
\"device_secret\": \"%s\",\
|
||||
\"device_name\": \"%s\"\
|
||||
},\
|
||||
\"rtc\": {\
|
||||
\"log_level\": 3,\
|
||||
\"audio\": {\
|
||||
\"publish\": true,\
|
||||
\"subscribe\": true,\
|
||||
\"codec\": 3\
|
||||
},\
|
||||
\"video\": {\
|
||||
\"publish\": false,\
|
||||
\"subscribe\": false,\
|
||||
\"codec\": 1\
|
||||
},\
|
||||
\"params\":[\
|
||||
\"{\\\"debug\\\":{\\\"log_to_console\\\":1}}\",\
|
||||
\"{\\\"rtc\\\":{\\\"access\\\":{\\\"concurrent_requests\\\":1}}}\",\
|
||||
\"{\\\"rtc\\\":{\\\"ice\\\":{\\\"concurrent_agents\\\":1}}}\",\
|
||||
\"{\\\"rtc\\\":{\\\"thread\\\":{\\\"stack_in_ext\\\":1}}}\",\
|
||||
\"{\\\"rtc\\\":{\\\"network\\\":{\\\"enable_audio_jitter2\\\":0}}}\",\
|
||||
\"{\\\"rtc\\\":{\\\"report\\\":{\\\"enable\\\":0}}}\",\
|
||||
\"{\\\"audio\\\":{\\\"codec\\\":{\\\"pcma\\\":{\\\"s_samples_per_frame\\\":480}}}}\"\
|
||||
]\
|
||||
}\
|
||||
}"
|
||||
|
||||
#define CONV_SERVICE_TIMEOUT 60
|
||||
|
||||
static char config_buf[1024] = {0};
|
||||
static volc_conv_service_t conv_service = {0};
|
||||
|
||||
static volatile bool is_stop = false;
|
||||
static volatile bool provider_disconnected = false;
|
||||
static volc_hal_display_t global_display = NULL;
|
||||
static bool session_started_reported = false;
|
||||
static QueueHandle_t platform_report_queue;
|
||||
static volatile bool diagnostic_say_pending;
|
||||
|
||||
void conv_ai_service_diagnostic_say(void)
|
||||
{
|
||||
diagnostic_say_pending = true;
|
||||
aios_event_pub(VOLC_SERVICE_AI_CONVERSATION, NULL, NULL);
|
||||
}
|
||||
|
||||
/* Implemented by the platform client in the top-level firmware component. */
|
||||
extern const char *platform_runtime_instance_id(void);
|
||||
extern const char *platform_runtime_product_key(void);
|
||||
extern const char *platform_runtime_device_name(void);
|
||||
extern const char *platform_runtime_device_secret(void);
|
||||
extern int platform_conversation_begin(void);
|
||||
extern const char *platform_last_error_code(void);
|
||||
extern const char *platform_conversation_bot_id(void);
|
||||
extern const char *platform_conversation_update_json(void);
|
||||
extern int platform_conversation_started(const char *provider_connection_ref);
|
||||
extern int platform_conversation_poll(void);
|
||||
extern int platform_conversation_turn(const char *speaker, const char *source_id, const char *text);
|
||||
extern void platform_conversation_finish(bool failed, const char *error_code);
|
||||
|
||||
volc_conv_service_t get_conv_ai_service()
|
||||
{
|
||||
return conv_service;
|
||||
}
|
||||
// use dump audio
|
||||
// volc_hal_file_t* audioFile = NULL;
|
||||
// int a_cnt = 0;
|
||||
static void __audio_capture_cb(volc_hal_capture_t capture, const void* data, int len, volc_hal_frame_info_t* frame_info){
|
||||
volc_audio_frame_info_t info = {0};
|
||||
info.commit = false;
|
||||
info.data_type = frame_info->data_type;
|
||||
if(!is_stop)
|
||||
{
|
||||
static uint64_t report_ms, magnitude;
|
||||
static unsigned samples, peak, frames, failures, max_send_ms;
|
||||
const int16_t *pcm = data;
|
||||
if (frame_info->data_type == VOLC_AUDIO_DATA_TYPE_PCM) {
|
||||
for (int i = 0; i < len / sizeof(int16_t); ++i) {
|
||||
unsigned value = pcm[i] < 0 ? -(int)pcm[i] : pcm[i];
|
||||
magnitude += value;
|
||||
if (value > peak) peak = value;
|
||||
samples++;
|
||||
}
|
||||
}
|
||||
uint64_t before = volc_osal_get_time_ms();
|
||||
int sent = volc_send_audio_data(conv_service.engine, data, len, &info);
|
||||
unsigned elapsed = volc_osal_get_time_ms() - before;
|
||||
if (elapsed > max_send_ms) max_send_ms = elapsed;
|
||||
frames++;
|
||||
if (sent < 0) failures++;
|
||||
if (before - report_ms >= 2000) {
|
||||
int rssi = get_rssi();
|
||||
LOGI("UPLINK mean=%u peak=%u frames=%u failed=%u max_send_ms=%u rssi=%d",
|
||||
samples ? (unsigned)(magnitude / samples) : 0, peak, frames, failures, max_send_ms, rssi);
|
||||
report_ms = before;
|
||||
magnitude = samples = peak = frames = failures = max_send_ms = 0;
|
||||
}
|
||||
// if(audioFile == NULL){
|
||||
// audioFile = volc_hal_file_open("/sdcard/cap.pcm","wb");
|
||||
// volc_hal_file_write(audioFile,data,len);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
static void __on_volc_event(volc_engine_t handle, volc_event_t *event, void *user_data)
|
||||
{
|
||||
switch (event->code)
|
||||
{
|
||||
case VOLC_EV_CONNECTED:
|
||||
LOGI("Volc Engine connected");
|
||||
break;
|
||||
case VOLC_EV_DISCONNECTED:
|
||||
LOGI( "Volc Engine disconnected");
|
||||
provider_disconnected = true;
|
||||
is_stop = true;
|
||||
break;
|
||||
default:
|
||||
LOGI("Volc Engine event: %d", event->code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void __on_volc_conversation_status(volc_engine_t handle, volc_conv_status_e status, void *user_data)
|
||||
{
|
||||
int index = MMAP_EAF_HAPPY_EAF;
|
||||
switch (status)
|
||||
{
|
||||
case VOLC_CONV_STATUS_LISTENING:
|
||||
index = MMAP_EAF_EMOTION_BLINK_FAST_284_126_EAF;
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_MAIN,VOLC_DISPLAY_IMAGE,&index);
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_STATUS,VOLC_DISPLAY_TEXT,"智能体聆听中");
|
||||
break;
|
||||
case VOLC_CONV_STATUS_THINKING:
|
||||
index = MMAP_EAF_EMOTION_DIZZY_284_126_EAF;
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_MAIN,VOLC_DISPLAY_IMAGE,&index);
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_STATUS,VOLC_DISPLAY_TEXT,"智能体思考中");
|
||||
break;
|
||||
case VOLC_CONV_STATUS_ANSWERING:
|
||||
index = MMAP_EAF_HAPPY_EAF;
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_MAIN,VOLC_DISPLAY_IMAGE,&index);
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_STATUS,VOLC_DISPLAY_TEXT,"智能体说话中");
|
||||
break;
|
||||
case VOLC_CONV_STATUS_INTERRUPTED:
|
||||
{
|
||||
volc_hal_context_t *hal = volc_get_global_hal_context();
|
||||
if (hal) volc_hal_player_discard_pending(hal->player_handle[VOLC_HAL_PLAYER_AUDIO]);
|
||||
}
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_STATUS,VOLC_DISPLAY_TEXT,"智能体被打断");
|
||||
break;
|
||||
case VOLC_CONV_STATUS_ANSWER_FINISH:
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_STATUS,VOLC_DISPLAY_TEXT,"智能体说话完成");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static void __on_volc_audio_data(volc_engine_t handle, const void *data_ptr, size_t data_len, volc_audio_frame_info_t *info_ptr, void *user_data)
|
||||
{
|
||||
int error = 0;
|
||||
volc_hal_context_t* g_hal_context = volc_get_global_hal_context();
|
||||
if (g_hal_context == NULL) {
|
||||
LOGE("volc_get_global_hal_context failed");
|
||||
return;
|
||||
}
|
||||
|
||||
volc_hal_player_t player_handle = g_hal_context->player_handle[VOLC_HAL_PLAYER_AUDIO];
|
||||
|
||||
if (player_handle == NULL)
|
||||
{
|
||||
LOGE("player pipeline is NULL");
|
||||
return;
|
||||
}
|
||||
if(!is_stop){
|
||||
volc_hal_player_play_data(player_handle, data_ptr, data_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void __on_volc_video_data(volc_engine_t handle, const void *data_ptr, size_t data_len, volc_video_frame_info_t *info_ptr, void *user_data)
|
||||
{
|
||||
}
|
||||
|
||||
static int sub_offset = 0;
|
||||
static void __on_subtitle_message_received(const cJSON* root) {
|
||||
/*
|
||||
{
|
||||
"data" :
|
||||
[
|
||||
{
|
||||
"definite" : false,
|
||||
"language" : "zh",
|
||||
"mode" : 1,
|
||||
"paragraph" : false,
|
||||
"sequence" : 0,
|
||||
"text" : "\\u4f60\\u597d",
|
||||
"userId" : "voiceChat_xxxxx"
|
||||
}
|
||||
],
|
||||
"type" : "subtitle"
|
||||
}
|
||||
*/
|
||||
cJSON * type_obj = cJSON_GetObjectItem(root, "type");
|
||||
if (type_obj != NULL && strcmp("subtitle", cJSON_GetStringValue(type_obj)) == 0) {
|
||||
cJSON* data_obj_arr = cJSON_GetObjectItem(root, "data");
|
||||
cJSON* obji = NULL;
|
||||
cJSON_ArrayForEach(obji, data_obj_arr) {
|
||||
cJSON* user_id_obj = cJSON_GetObjectItem(obji, "userId");
|
||||
cJSON* text_obj = cJSON_GetObjectItem(obji, "text");
|
||||
cJSON* definite_obj = cJSON_GetObjectItem(obji, "definite");
|
||||
char* sub = cJSON_GetStringValue(text_obj);
|
||||
|
||||
// print_string(sub + sub_offset -1);
|
||||
if(cJSON_IsTrue(definite_obj)){
|
||||
sub_offset = sub_offset == 0 ? 0 : sub_offset -1;
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_SUBTITLE,VOLC_DISPLAY_TEXT,sub + sub_offset);
|
||||
sub_offset = 0;
|
||||
aios_event_pub(VOLC_LOCAL_LOGIC_PROCESS_SUBTITLE, sub, NULL);
|
||||
} else if(strlen(sub) - sub_offset > 18){
|
||||
sub_offset = sub_offset == 0 ? 0 : sub_offset -1;
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_SUBTITLE,VOLC_DISPLAY_TEXT,sub + sub_offset);
|
||||
if(strlen(sub) - sub_offset > 36){
|
||||
sub_offset = strlen(sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* HTTPS reporting must never run inside the WebSocket receive mutex. */
|
||||
static void process_platform_reports(void)
|
||||
{
|
||||
cJSON *root;
|
||||
while (platform_report_queue && xQueueReceive(platform_report_queue, &root, 0) == pdTRUE) {
|
||||
cJSON *type_obj = cJSON_GetObjectItemCaseSensitive(root, "type");
|
||||
const char *type = cJSON_IsString(type_obj) ? type_obj->valuestring : "";
|
||||
if ((strcmp(type, "session.updated") == 0 || strcmp(type, "session.created") == 0) && !session_started_reported) {
|
||||
cJSON *session = cJSON_GetObjectItemCaseSensitive(root, "session");
|
||||
cJSON *id = cJSON_IsObject(session) ? cJSON_GetObjectItemCaseSensitive(session, "id") : NULL;
|
||||
cJSON *job_id = cJSON_GetObjectItemCaseSensitive(root, "job_id");
|
||||
const char *provider_ref = cJSON_IsString(id) && id->valuestring[0] ? id->valuestring :
|
||||
(cJSON_IsString(job_id) ? job_id->valuestring : NULL);
|
||||
if (provider_ref && provider_ref[0] && platform_conversation_started(provider_ref) == 0) {
|
||||
session_started_reported = true;
|
||||
LOGI("Platform confirmed dynamic session configuration");
|
||||
}
|
||||
} else if (strcmp(type, "conversation.item.input_audio_transcription.completed") == 0) {
|
||||
cJSON *text = cJSON_GetObjectItemCaseSensitive(root, "transcript");
|
||||
cJSON *source = cJSON_GetObjectItemCaseSensitive(root, "event_id");
|
||||
if (cJSON_IsString(text) && text->valuestring[0]) {
|
||||
(void)platform_conversation_turn("subject", cJSON_IsString(source) ? source->valuestring : "subject", text->valuestring);
|
||||
}
|
||||
} else if (strcmp(type, "response.audio_transcript.done") == 0) {
|
||||
cJSON *text = cJSON_GetObjectItemCaseSensitive(root, "transcript");
|
||||
cJSON *source = cJSON_GetObjectItemCaseSensitive(root, "response_id");
|
||||
if (cJSON_IsString(text) && text->valuestring[0]) {
|
||||
(void)platform_conversation_turn("agent", cJSON_IsString(source) ? source->valuestring : "agent", text->valuestring);
|
||||
}
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
}
|
||||
|
||||
static void __on_volc_message_data(volc_engine_t handle, const void *message, size_t size, volc_message_info_t *info_ptr, void *user_data)
|
||||
{
|
||||
static char message_buffer[4096];
|
||||
if (size > 0 && size < sizeof(message_buffer) - 1) {
|
||||
memcpy(message_buffer, message, size);
|
||||
message_buffer[size] = 0;
|
||||
const bool standard_protocol = message_buffer[0] == '{';
|
||||
if (!standard_protocol && size < 8) return;
|
||||
cJSON *root = cJSON_Parse(standard_protocol ? message_buffer : message_buffer + 8);
|
||||
if (root != NULL) {
|
||||
if (standard_protocol) {
|
||||
const char *type = cJSON_GetStringValue(cJSON_GetObjectItem(root, "type"));
|
||||
if (type && (!strcmp(type, "error") || !strcmp(type, "conversation.item.created"))) {
|
||||
LOGI("Provider control event: %s", message_buffer);
|
||||
}
|
||||
if (type && !strcmp(type, "response.audio.done") && !is_stop) {
|
||||
/* Flush the final sub-frame through the PCM decoder. */
|
||||
static const uint8_t silence[2048] = {0};
|
||||
volc_hal_context_t *hal = volc_get_global_hal_context();
|
||||
if (hal) {
|
||||
volc_hal_player_t player = hal->player_handle[VOLC_HAL_PLAYER_AUDIO];
|
||||
volc_hal_player_play_data(player, silence, sizeof(silence));
|
||||
volc_hal_player_finish_segment(player);
|
||||
}
|
||||
}
|
||||
if (type && (strstr(type, "speech_") || strstr(type, "response."))) {
|
||||
conv_service.wait_time = 0;
|
||||
}
|
||||
bool report = type && (!strcmp(type, "session.created") || !strcmp(type, "session.updated") ||
|
||||
!strcmp(type, "conversation.item.input_audio_transcription.completed") ||
|
||||
!strcmp(type, "response.audio_transcript.done"));
|
||||
if (report && platform_report_queue && xQueueSend(platform_report_queue, &root, 0) == pdTRUE) {
|
||||
root = NULL; /* Conversation task owns queued messages. */
|
||||
} else if (report) {
|
||||
LOGE("Platform report queue unavailable or full");
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
} else if (message_buffer[0] == 's' && message_buffer[1] == 'u' && message_buffer[2] == 'b' && message_buffer[3] == 'v') {
|
||||
__on_subtitle_message_received(root);
|
||||
conv_service.wait_time = 0;
|
||||
} else if (message_buffer[0] == 't' && message_buffer[1] == 'o' && message_buffer[2] == 'o' && message_buffer[3] == 'l') {
|
||||
// function calling 消息
|
||||
aios_event_pub(VOLC_FUNCTION_CALL_EXEC, root, NULL);
|
||||
}else if (message_buffer[0] == 'i' && message_buffer[1] == 'n' && message_buffer[2] == 'f' && message_buffer[3] == 'o') {
|
||||
aios_event_pub(VOLC_FUNCTION_CALL_TRIGGER, root, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void conv_ai_service_init()
|
||||
{
|
||||
if (!platform_report_queue) platform_report_queue = xQueueCreate(8, sizeof(cJSON *));
|
||||
snprintf(config_buf, sizeof(config_buf), CONV_AI_CONFIG_FORMAT,
|
||||
platform_runtime_instance_id(),
|
||||
platform_runtime_product_key(),
|
||||
platform_runtime_device_secret(),
|
||||
platform_runtime_device_name());
|
||||
|
||||
conv_service.volc_event_handler = (volc_event_handler_t){
|
||||
.on_volc_event = __on_volc_event,
|
||||
.on_volc_conversation_status = __on_volc_conversation_status,
|
||||
.on_volc_audio_data = __on_volc_audio_data,
|
||||
.on_volc_video_data = __on_volc_video_data,
|
||||
.on_volc_message_data = __on_volc_message_data,
|
||||
};
|
||||
|
||||
int ret = volc_create(&conv_service.engine, config_buf, &conv_service.volc_event_handler, &conv_service);
|
||||
if (ret != 0) {
|
||||
LOGE("volc_create failed, ret: %d", ret);
|
||||
return;
|
||||
} else {
|
||||
LOGE("volc_create success");
|
||||
}
|
||||
}
|
||||
|
||||
void conv_ai_service_task(void *pvParameters)
|
||||
{
|
||||
volc_hal_context_t* g_hal_context = volc_get_global_hal_context();
|
||||
char* status_str = NULL;
|
||||
bool provider_started = false;
|
||||
bool audio_started = false;
|
||||
session_started_reported = false;
|
||||
provider_disconnected = false;
|
||||
if (g_hal_context == NULL) {
|
||||
LOGE("volc_get_global_hal_context failed");
|
||||
status_str = "系统未初始化,ai对话连接失败";
|
||||
goto CONV_AI_QUIT;
|
||||
}
|
||||
global_display = g_hal_context->display_handle;
|
||||
int ret = 0;
|
||||
volc_hal_display_set_content(global_display, VOLC_DISPLAY_OBJ_STATUS,VOLC_DISPLAY_TEXT, "ai对话创建中");
|
||||
|
||||
ret = platform_conversation_begin();
|
||||
if (ret != 0) {
|
||||
LOGE("platform_conversation_begin failed, ret: %d", ret);
|
||||
status_str = "平台会话创建失败";
|
||||
if (strcmp(platform_last_error_code(), "FIRMWARE_UPDATE_REQUIRED") == 0) {
|
||||
status_str = "平台要求更新固件";
|
||||
}
|
||||
goto CONV_AI_QUIT;
|
||||
}
|
||||
|
||||
/* Capture may run while the WebSocket is still handshaking. Hold frames
|
||||
* until volc_start has moved the engine into STARTED state. */
|
||||
is_stop = true;
|
||||
|
||||
// step 1: start audio capture & play
|
||||
volc_hal_capture_config_t capture_audio_config = {0};
|
||||
capture_audio_config.media_type = VOLC_MEDIA_TYPE_AUDIO;
|
||||
capture_audio_config.data_cb = __audio_capture_cb;
|
||||
// capture_audio_config.audio_wakeup_cb = (volc_hal_audio_wakeup_cb_t)rec_engine_cb;
|
||||
|
||||
capture_audio_config.user_data = g_hal_context;
|
||||
volc_hal_capture_t capture_handle = g_hal_context->capture_handle[VOLC_HAL_PLAYER_AUDIO];
|
||||
if (g_hal_context->capture_handle[VOLC_HAL_PLAYER_AUDIO] == NULL) {
|
||||
g_hal_context->capture_handle[VOLC_HAL_PLAYER_AUDIO] = volc_hal_capture_create(&capture_audio_config);
|
||||
capture_handle = g_hal_context->capture_handle[VOLC_HAL_PLAYER_AUDIO];
|
||||
}
|
||||
audio_started = true;
|
||||
volc_hal_capture_stop(g_hal_context->capture_handle[VOLC_HAL_CAPTURE_AUDIO]);
|
||||
volc_hal_capture_update_config(capture_handle, &capture_audio_config);
|
||||
volc_hal_capture_start(g_hal_context->capture_handle[VOLC_HAL_PLAYER_AUDIO], VOLC_AUDIO_MODE_CAPTURE);
|
||||
|
||||
volc_hal_player_config_t player_audio_config = {0};
|
||||
player_audio_config.media_type = VOLC_MEDIA_TYPE_AUDIO;
|
||||
if (g_hal_context->player_handle[VOLC_HAL_PLAYER_AUDIO] == NULL) {
|
||||
g_hal_context->player_handle[VOLC_HAL_PLAYER_AUDIO] = volc_hal_player_create(&player_audio_config);
|
||||
}
|
||||
|
||||
volc_hal_display_set_content(global_display, VOLC_DISPLAY_OBJ_STATUS, VOLC_DISPLAY_TEXT, "ai对话连接中");
|
||||
volc_hal_player_start(g_hal_context->player_handle[VOLC_HAL_PLAYER_AUDIO]);
|
||||
|
||||
// step 2: start ai conversation
|
||||
const char *session_update = platform_conversation_update_json();
|
||||
size_t params_size = strlen(session_update) + 64;
|
||||
char *params = volc_osal_malloc(params_size);
|
||||
if (params == NULL) {
|
||||
status_str = "会话配置内存不足";
|
||||
goto CONV_AI_QUIT;
|
||||
}
|
||||
snprintf(params, params_size, "{\"audio\":{\"codec\":0},\"session_update\":%s}", session_update);
|
||||
volc_opt_t opt = {
|
||||
.mode = VOLC_MODE_WS,
|
||||
.bot_id = (char *)platform_conversation_bot_id(),
|
||||
.params = params,
|
||||
};
|
||||
#if defined ENABLE_PLAY_WELCOME
|
||||
aios_event_pub(VOLC_LOCAL_LOGIC_PLAY_WELCOME, NULL, NULL);
|
||||
#endif
|
||||
ret = volc_start(conv_service.engine, &opt);
|
||||
volc_osal_free(params);
|
||||
if (ret != 0) {
|
||||
LOGE("volc_start failed, ret: %d", ret);
|
||||
status_str = "ai对话连接失败,准备退出";
|
||||
goto CONV_AI_QUIT;
|
||||
}
|
||||
provider_started = true;
|
||||
volc_hal_display_set_content(global_display, VOLC_DISPLAY_OBJ_STATUS, VOLC_DISPLAY_TEXT, "ai对话已连接");
|
||||
is_stop = false;
|
||||
|
||||
while (!is_stop && !provider_disconnected) {
|
||||
process_platform_reports();
|
||||
if (session_started_reported && platform_conversation_poll()) {
|
||||
status_str = "平台已结束本次对话";
|
||||
goto CONV_AI_QUIT;
|
||||
}
|
||||
if (diagnostic_say_pending && session_started_reported) {
|
||||
diagnostic_say_pending = false;
|
||||
char request[640];
|
||||
snprintf(request, sizeof(request), "{\"event_id\":\"diag-%llu\",\"type\":\"conversation.item.create\",\"item\":{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"请只说这句话:你好,这是语音播放测试。现在使用自然的语速,连续清楚地说完这句话。\"}],\"interrupt_mode\":1}}", (unsigned long long)volc_osal_get_time_ms());
|
||||
volc_message_info_t info = {.is_binary = false};
|
||||
int sent = volc_send_message(conv_service.engine, request, strlen(request), &info);
|
||||
LOGI("PLAYBACK diagnostic sentence requested ret=%d", sent);
|
||||
const char *respond = "{\"type\":\"response.create\",\"response\":{\"modalities\":[\"text\",\"audio\"]}}";
|
||||
volc_send_message(conv_service.engine, respond, strlen(respond), &info);
|
||||
conv_service.wait_time = 0;
|
||||
}
|
||||
sleep(1);
|
||||
conv_service.wait_time++;
|
||||
if(conv_service.wait_time >= CONV_SERVICE_TIMEOUT){
|
||||
status_str = "ai对话已超时,准备退出";
|
||||
goto CONV_AI_QUIT;
|
||||
}
|
||||
}
|
||||
status_str = "ai对话已断开";
|
||||
|
||||
CONV_AI_QUIT:
|
||||
is_stop = true;
|
||||
// volc_hal_file_close(audioFile);
|
||||
volc_stop(conv_service.engine);
|
||||
process_platform_reports();
|
||||
platform_conversation_finish(!provider_started || !session_started_reported,
|
||||
!provider_started ? "VOLC_START_FAILED" : (!session_started_reported ? "SESSION_UPDATE_NOT_CONFIRMED" : NULL));
|
||||
|
||||
if (g_hal_context && audio_started) {
|
||||
volc_hal_capture_stop(g_hal_context->capture_handle[VOLC_HAL_CAPTURE_AUDIO]);
|
||||
if (g_hal_context->player_handle[VOLC_HAL_PLAYER_AUDIO]) {
|
||||
volc_hal_player_stop(g_hal_context->player_handle[VOLC_HAL_PLAYER_AUDIO]);
|
||||
volc_hal_player_destroy(g_hal_context->player_handle[VOLC_HAL_PLAYER_AUDIO]);
|
||||
g_hal_context->player_handle[VOLC_HAL_PLAYER_AUDIO] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if(status_str != NULL){
|
||||
volc_hal_display_set_content(global_display, VOLC_DISPLAY_OBJ_STATUS, VOLC_DISPLAY_TEXT, status_str);
|
||||
}
|
||||
volc_hal_display_set_content(global_display, VOLC_DISPLAY_OBJ_SUBTITLE, VOLC_DISPLAY_TEXT, "");
|
||||
conv_service.wait_time = 0;
|
||||
// just show the status: Ai对话已断开
|
||||
sleep(1);
|
||||
if (g_hal_context && audio_started) {
|
||||
volc_hal_capture_start(g_hal_context->capture_handle[VOLC_HAL_CAPTURE_AUDIO],VOLC_AUDIO_MODE_WAKEUP);
|
||||
}
|
||||
aios_event_pub(VOLC_SERVICE_AI_CONVERSATION_OVER, NULL, NULL);
|
||||
volc_osal_thread_exit(NULL);
|
||||
}
|
||||
|
||||
void conv_ai_service_task_stop(void)
|
||||
{
|
||||
// sleep for AI Agent say goodbye
|
||||
sleep(1);
|
||||
is_stop = true;
|
||||
}
|
||||
|
||||
#if __cplusplus
|
||||
}
|
||||
#endif
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <stdio.h>
|
||||
|
||||
#include "aios.h"
|
||||
#include "volc_hal.h"
|
||||
#include "volc_osal.h"
|
||||
#include "volc_hal_display.h"
|
||||
#include "volc_hal_file.h"
|
||||
|
||||
#include "volc_service_common.h"
|
||||
#include "volc_conv_service.h"
|
||||
#include "volc_conv.h"
|
||||
|
||||
#if __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct volc_conv_service_manager {
|
||||
aios_session_t super;
|
||||
volc_osal_tid_t conv_thread_id;
|
||||
} volc_conv_service_manager_t;
|
||||
|
||||
volc_conv_service_manager_t volc_conv_service_manager;
|
||||
|
||||
static aios_ret_t __state_conversation(volc_conv_service_manager_t * const me, aios_event_t const * const e)
|
||||
{
|
||||
volc_hal_context_t* g_hal_context = volc_get_global_hal_context();
|
||||
if (g_hal_context == NULL) {
|
||||
return AIOS_Ret_NotHandled;
|
||||
}
|
||||
volc_hal_display_t global_display = g_hal_context->display_handle;
|
||||
switch (e->id) {
|
||||
case VOLC_SERVICE_AI_CONVERSATION_START:
|
||||
if (me->conv_thread_id == NULL) {
|
||||
volc_osal_thread_param_t param = {0};
|
||||
volc_hal_display_set_content(global_display,VOLC_DISPLAY_OBJ_STATUS,VOLC_DISPLAY_TEXT,"ai对话启动中");
|
||||
snprintf(param.name, sizeof(param.name), "%s", "conv_ai");
|
||||
param.stack_size = 8*1024;
|
||||
param.priority = 5;
|
||||
param.stack_in_ext = 1;
|
||||
volc_osal_thread_create(&me->conv_thread_id, ¶m, conv_ai_service_task,NULL);
|
||||
}
|
||||
return AIOS_Ret_Handled;
|
||||
case VOLC_SERVICE_AI_CONVERSATION_INTERRUPT:
|
||||
return AIOS_Ret_Handled;
|
||||
// control the Ai_Conversation task quit
|
||||
case VOLC_SERVICE_AI_CONVERSATION_QUIT:
|
||||
if (me->conv_thread_id != NULL) {
|
||||
conv_ai_service_task_stop();
|
||||
me->conv_thread_id = NULL;
|
||||
}
|
||||
return AIOS_Ret_Handled;
|
||||
// notice the Ai_Conversation over by task
|
||||
case VOLC_SERVICE_AI_CONVERSATION_OVER:
|
||||
if (me->conv_thread_id != NULL) {
|
||||
me->conv_thread_id = NULL;
|
||||
}
|
||||
return AIOS_Ret_Handled;
|
||||
default:
|
||||
return AIOS_Ret_NotHandled;
|
||||
}
|
||||
}
|
||||
|
||||
static aios_ret_t __state_init(volc_conv_service_manager_t * const me, aios_event_t const * const e)
|
||||
{
|
||||
AIOS_EVENT_SUB(VOLC_SERVICE_AI_CONVERSATION_START); // 订阅事件
|
||||
AIOS_EVENT_SUB(VOLC_SERVICE_AI_CONVERSATION_INTERRUPT); // 订阅事件
|
||||
AIOS_EVENT_SUB(VOLC_SERVICE_AI_CONVERSATION_QUIT); // 订阅事件
|
||||
AIOS_EVENT_SUB(VOLC_SERVICE_AI_CONVERSATION_OVER); // 订阅事件
|
||||
me->conv_thread_id = NULL;
|
||||
conv_ai_service_init();
|
||||
return AIOS_TRAN(__state_conversation);
|
||||
}
|
||||
|
||||
void volc_conv_service_manager_init(void)
|
||||
{
|
||||
aios_session_init(&volc_conv_service_manager.super, VOLC_SERVICE_PRIORITY_AI_CONVERSATION, NULL);
|
||||
aios_session_start(&volc_conv_service_manager.super, AIOS_STATE_CAST(__state_init));
|
||||
}
|
||||
|
||||
#if __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user