mcu_hi3321_watch/tjd/ui/app/lefun_ai/TjdUiAppLefunAiModel.cpp
2025-05-26 20:15:20 +08:00

676 lines
22 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "TjdUiAppLefunAiModel.h"
#include "TjdUiAppLefunAiPresenter.h"
#include "TjdUiAppLefunAiView.h"
#include "TjdUiImageIds.h"
#include "TjdUiScreenManage.h"
#include "ble_port_protocol.h"
#include "cJSON.h"
#include "common/image_cache_manager.h"
#include "graphic_service.h"
#include "hal_tick.h"
#include "power_display_service.h"
#include "sql_setting.h"
#include "sys_config.h"
#include "thread_adapter.h"
#include <atomic>
#include <cstring>
#include <dirent.h>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
namespace TJD {
#define ENABLE_PRINT_INFO 0
#if ENABLE_PRINT_INFO
#define static_print_info(...) sys_ui_log_i(__VA_ARGS__) // 一般信息打印宏控制
#define static_print_warn(...) sys_ui_log_w(__VA_ARGS__) // 警告信息打印一般常开
#define static_print_error(...) sys_ui_log_e(__VA_ARGS__) // 错误信息打印一般常开
#define static_print_debug(...) sys_ui_log_d(__VA_ARGS__) // 调试信息打印一般常开
#else
#define static_print_info(...)
#define static_print_warn(...)
#define static_print_error(...)
#define static_print_debug(...)
#endif
#define LOAD_BUFFER_SIZE 8 * 1024
#define LEFUN_AI_SCREEN_AUTO_OFF_TIME (1000 * 60 * 60)
static MutexId g_lefun_ai_mutex = 0;
static bool story_question_flag = false;
static bool story_answer_flag = false;
struct data_flow *data_flow = nullptr;
std::string question = "";
std::string answer = "";
extern osThreadId_t resend_audio_data_id;
int TjdUiAppLefunAiModel::file_exists()
{
FILE *file = fopen(LEFUN_AI_STORY_FILE_PATH, "r");
if (file) {
fclose(file);
return 1; // 文件存在
}
return 0; // 文件不存在
}
void TjdUiAppLefunAiModel::addRecord(History *history, std::string question, std::string answer)
{
if (history == nullptr || history->records == nullptr) {
return;
}
if (history->total_records >= MAX_HISTORY) {
for (int i = 1; i < MAX_HISTORY; i++) {
history->records[i - 1] = history->records[i];
}
history->total_records--;
}
if (history->total_records < MAX_HISTORY) {
history->records[history->total_records].question = question;
history->records[history->total_records].answer = answer;
history->total_records++;
}
}
void TjdUiAppLefunAiModel::saveToJson(const History *history, const char *filename)
{
// 检查输入参数
if (!history || history->total_records < 0) {
printf("Error: Invalid history object\n");
return;
}
// 创建 JSON 对象
cJSON *json = cJSON_CreateObject();
if (!json) {
printf("Error creating JSON object\n");
return;
}
// 创建历史记录数组
cJSON *historyArray = cJSON_CreateArray();
if (!historyArray) {
printf("Error creating JSON array\n");
cJSON_Delete(json);
return;
}
// 将 total_records 添加到 JSON 对象中
cJSON_AddNumberToObject(json, "total_records", history->total_records);
// 遍历历史记录并添加到 JSON 数组中
for (int i = 0; i < history->total_records; i++) {
const std::string &question = history->records[i].question;
const std::string &answer = history->records[i].answer;
cJSON *item = cJSON_CreateObject();
if (!item) {
printf("Error creating JSON item for record \n");
continue;
}
if (item == nullptr) {
return;
}
cJSON_AddStringToObject(item, "question", question.c_str());
cJSON_AddStringToObject(item, "answer", answer.c_str());
// 将记录添加到历史记录数组
cJSON_AddItemToArray(historyArray, item);
}
// 将历史记录数组添加到 JSON 对象
cJSON_AddItemToObject(json, "history", historyArray);
// 打开文件以写入 JSON 数据
FILE *file = fopen(filename, "w");
if (!file) {
printf("Error opening file: ");
cJSON_Delete(json);
return;
}
// 将 JSON 对象转换为字符串并写入文件
char *jsonString = cJSON_Print(json);
if (jsonString) {
fprintf(file, "%s\n", jsonString);
free(jsonString); // 释放 JSON 字符串
}
// 关闭文件和删除 JSON 对象
fclose(file);
cJSON_Delete(json);
}
void TjdUiAppLefunAiModel::displayHistory(const History *history)
{
printf("displayHistory %d\n", history->total_records);
for (int i = 0; i < history->total_records; i++) {
// 打印每个记录的 question 和 answer
printf("Record %d:\n", i + 1);
printf("Question: %s\n", history->records[i].question.c_str());
printf("Answer: %s\n", history->records[i].answer.c_str());
printf("------------------\n");
}
}
void TjdUiAppLefunAiModel::loadFromJson(History *history)
{
char *filename = LEFUN_AI_STORY_FILE_PATH;
FILE *file = fopen(filename, "r");
if (!file) {
// 文件不存在,创建初始 JSON
createInitialJson(filename);
return; // 返回以避免进一步处理
}
char *buffer = (char *)malloc(LOAD_BUFFER_SIZE);
if (!buffer) {
printf("Error: Failed to allocate memory for buffer.\n");
fclose(file);
return;
}
size_t bytesRead = fread(buffer, sizeof(char), LOAD_BUFFER_SIZE - 1, file);
buffer[bytesRead] = '\0';
fclose(file);
cJSON *json = cJSON_Parse(buffer);
if (json) {
cJSON *totalRecordsItem = cJSON_GetObjectItem(json, "total_records");
history->total_records = totalRecordsItem ? totalRecordsItem->valueint : 0;
cJSON *historyArray = cJSON_GetObjectItem(json, "history");
cJSON *record = NULL;
int index = 0; // 当前存储记录的索引
cJSON_ArrayForEach(record, historyArray)
{
if (index < 10) {
// 获取问题和答案
cJSON *questionItem = cJSON_GetObjectItem(record, "question");
cJSON *answerItem = cJSON_GetObjectItem(record, "answer");
if (questionItem && questionItem->valuestring && answerItem && answerItem->valuestring) {
history->records[index].question = questionItem->valuestring;
history->records[index].answer = answerItem->valuestring;
index++;
} else {
fprintf(stderr, "Warning: Missing question or answer in record %d\n", index);
}
}
history->total_records = index;
}
cJSON_Delete(json);
} else {
static_print_warn("Error parsing JSON json null\n");
}
free(buffer);
}
uint8_t TjdUiAppLefunAiModel::get_total_records_from_json()
{
FILE *file = fopen(LEFUN_AI_STORY_FILE_PATH, "r");
if (file == NULL) {
printf("Could not open file %s\n", LEFUN_AI_STORY_FILE_PATH);
return 0; // 返回 0 表示文件未未初始化,直接返回
}
// 获取文件大小
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
// 读取文件内容
char *data = (char *)malloc(length + 1);
fread(data, 1, length, file);
data[length] = '\0'; // 确保字符串以 null 结尾
fclose(file);
cJSON *json = cJSON_Parse(data);
free(data);
if (json == NULL) {
printf("Error parsing JSON\n");
return -1; // 返回 -1 表示解析错误
}
cJSON *total_records_item = cJSON_GetObjectItem(json, "total_records");
uint8_t total_records = total_records_item ? total_records_item->valueint : -1; // 默认返回 -1
cJSON_Delete(json);
return total_records;
}
void TjdUiAppLefunAiModel::createInitialJson(const char *filename)
{
cJSON *json = cJSON_CreateObject();
cJSON_AddNumberToObject(json, "total_records", 0);
cJSON *historyArray = cJSON_CreateArray();
cJSON_AddItemToObject(json, "history", historyArray);
FILE *file = fopen(filename, "w");
if (file) {
char *jsonString = cJSON_Print(json);
fprintf(file, "%s\n", jsonString);
free(jsonString);
fclose(file);
}
cJSON_Delete(json);
}
void TjdUiAppLefunAiModel::AudioInit(void)
{
if (audioInitStatus_) {
static_print_info("LefunAiModel audioInitStatus_ is true");
return;
}
Audio::AudioManager &amIntance = Audio::AudioManager::GetInstance();
audioInitStatus_ = amIntance.Initialize();
playerCtr_ = std::make_shared<OHOS::Media::Player>();
// playerCallback_ = std::make_shared<RecordPlayerCallbackImpl>();
static_print_info("LefunAiModel AudioInit end!");
}
int TjdUiAppLefunAiModel::RecordStart()
{
if (recordIsRuning_) {
static_print_warn("LefunAiModel RecordStart failed, recordIsRuning_ is true");
return OHOS_FAILURE;
}
Audio::AudioManager &amIntance = Audio::AudioManager::GetInstance();
if (!isActivateAudioInterrupt_) {
static_print_info("ActivateAudioInterrupt");
// 申请焦点
sessionId_ = amIntance.MakeSessionId();
if (sessionId_ == AUDIO_SESSION_ID_NONE) {
static_print_warn("session invalid");
return OHOS_FAILURE;
}
std::shared_ptr<LefunAiInterruptListener> recordInterruptListener_ =
std::make_shared<LefunAiInterruptListener>();
interrupt_ = {AUDIO_STREAM_VOICE_RECORD, sessionId_, recordInterruptListener_};
// 激活音频中断 5s超时
if (amIntance.ActivateAudioInterrupt(interrupt_) == INTERRUPT_FAILED) {
static_print_warn("ActivateAudioInterrupt faild");
return OHOS_FAILURE;
}
isActivateAudioInterrupt_ = true;
}
lefunAiCapturer_ = std::make_shared<OHOS::Audio::AudioCapturer>();
url_ = std::string("/user/") + "lefunAi_test.opus";
pfd_.open(url_, std::ios::out | std::ios::binary);
if (!pfd_.is_open()) {
static_print_warn("open file failed");
lefunAiCapturer_.reset();
(void)amIntance.DeactivateAudioInterrupt(interrupt_);
return -1;
}
// 设置音频参数
OHOS::Audio::AudioCapturerInfo info = {};
info.inputSource = AUDIO_MIC;
info.audioFormat = AudioCodecFormat::OPUS;
info.sampleRate = 16000;
info.channelCount = 1;
info.streamType = AUDIO_STREAM_VOICE_RECORD;
info.bitWidth = AudioBitWidth::BIT_WIDTH_16;
info.sessionID = sessionId_;
if (lefunAiCapturer_->SetCapturerInfo(info) != 0) {
static_print_warn("{%s,%d} set capturer info failed", __func__, __LINE__);
(void)amIntance.DeactivateAudioInterrupt(interrupt_);
return -1;
}
// 获取每帧大小
size_t frameCount = lefunAiCapturer_->GetFrameCount();
if (!frameCount) {
static_print_warn("Can't GetFrameCount");
(void)lefunAiCapturer_->Release();
(void)amIntance.DeactivateAudioInterrupt(interrupt_);
return -1;
}
framesize_ = frameCount * info.channelCount * info.bitWidth / 8;
static_print_info("framesize_:%d", framesize_);
lefunAiRecordBuffer_ = new uint8_t[framesize_];
if (!lefunAiCapturer_->Start()) {
static_print_warn("{%s,%d} start failed", __func__, __LINE__);
lefunAiCapturer_.reset();
(void)amIntance.DeactivateAudioInterrupt(interrupt_);
// delete lefunAiCapturer_;
return -1;
}
recordIsRuning_ = false;
MediaThreadattr attr = {"lefun_ai_record", 0x2000, THREAD_SCHED_INVALID, 0, true};
threadIdHandle_ = MediaThreadCreate(
[](void *arg) -> void * {
TjdUiAppLefunAiModel::GetInstance().AudioCaptureProcess();
return nullptr;
},
NULL, &attr);
if (threadIdHandle_ == nullptr) {
static_print_warn("thread create failed");
return -1;
}
return 0;
}
void TjdUiAppLefunAiModel::RecordStop()
{
static_print_info("RecordStop");
if (recordIsRuning_) {
{
std::lock_guard<std::mutex> lock(pauseMutex_);
needStop_.store(true);
needPause_.store(false); // 确保线程不会因为暂停而阻塞
}
pauseCondVar_.notify_all(); // 唤醒等待的线程
static_print_debug("LefunAi RecordStop wait thread join");
osThreadJoin(threadIdHandle_);
static_print_debug("LefunAi RecordStop thread join end");
}
LefunAiRequestEndAudioTransmit();
}
void TjdUiAppLefunAiModel::AudioCaptureProcess(void)
{
static_print_info("TjdUiAppLefunAiModel AudioCaptureProcess start");
recordIsRuning_ = true;
while (!needStop_) {
// 检查是否需要暂停和停止
std::unique_lock<std::mutex> lock(pauseMutex_);
uint64_t pauseStartTime = OHOS::HALTick::GetInstance().GetTime();
pauseCondVar_.wait(lock, [this] { return !needPause_ || needStop_; });
if (needStop_) {
static_print_debug("needStop_ is true");
break;
}
memset(lefunAiRecordBuffer_, 0, framesize_);
ret = lefunAiCapturer_->Read(lefunAiRecordBuffer_, framesize_, false);
printf("lefunAiCapturer_->Read ret:%d\n", ret);
if (ret == -1) {
static_print_warn("audioCap Read failed:0x%x", ret);
continue;
}
if (ret == OHOS::Media::ERR_RETRY_READ) {
usleep(10000);
continue;
}
pfd_.write((const char *)lefunAiRecordBuffer_, ret);
if (!pfd_) {
static_print_warn("errno:%d, errmsg:%s", errno, strerror(errno));
break;
}
TransmitAudioData(lefunAiRecordBuffer_ + 8, ret - 8);
}
static_print_info("AudioCaptureProcess end");
pfd_.close();
RecordDestroy();
}
static void resend_audio_task(void *argument)
{
osDelay(500);
// 打开文件
std::ifstream inputFile("/user/lefunAi_test.opus", std::ios::in | std::ios::binary);
if (!inputFile.is_open()) {
printf("Failed to open file: %s", "/user/lefunAi_test.opus");
return;
}
// 定义缓冲区每次读取168字节
const size_t bufferSize = 168;
uint8_t buffer[168];
// 循环读取文件并发送数据
while (inputFile.read(reinterpret_cast<char *>(buffer), bufferSize)) {
size_t bytesRead = inputFile.gcount(); // 获取实际读取的字节数
if (bytesRead > 8) {
// 只传输从第9个字节开始的数据
printf("Read %zu bytes from file\n", bytesRead);
TjdUiAppLefunAiModel::GetInstance().TransmitAudioData(buffer + 8,
bytesRead - 8); // 发送数据跳过前8个字节
osDelay(20); // 等待20ms防止数据过快导致传输失败
}
}
printf("AudioCaptureFromFile end");
inputFile.close();
TjdUiAppLefunAiModel::GetInstance().LefunAiRequestEndAudioTransmit();
osThreadTerminate(resend_audio_data_id);
}
void TjdUiAppLefunAiModel::ResendAudioData()
{
static_print_info("ResendAudioData start");
osThreadAttr_t task_attr = {"resend_audio_data_task", 0, NULL, 0, NULL, 0x1000, (osPriority_t)17, 0, 0};
resend_audio_data_id = osThreadNew(resend_audio_task, NULL, &task_attr);
if (resend_audio_data_id == NULL) {
static_print_error("resend_audio_data_task failed");
return;
}
}
void TjdUiAppLefunAiModel::RecordDestroy(void)
{
static_print_debug("RecordDestroy");
Audio::AudioManager &amIntance = Audio::AudioManager::GetInstance();
MediaMutexLock(g_lefun_ai_mutex);
if (!lefunAiCapturer_->Stop()) {
static_print_warn("Stop failed");
}
if (!lefunAiCapturer_->Release()) {
static_print_warn("Release failed");
}
delete lefunAiRecordBuffer_;
lefunAiRecordBuffer_ = nullptr;
if (amIntance.DeactivateAudioInterrupt(interrupt_) != 0) {
static_print_warn("deactivate audio interrupt failed");
}
needStop_ = false;
needPause_ = false;
lefunAiCapturer_.reset();
lefunAiCapturer_ = nullptr;
isActivateAudioInterrupt_ = false;
recordIsRuning_ = false;
MediaMutexUnLock(g_lefun_ai_mutex);
}
void TjdUiAppLefunAiModel::RequestIntoLefunAi()
{
tjd_lefun_ai_request_into_lefun_ai();
tjd_ble_upload_ai_func_attribute();
}
void TjdUiAppLefunAiModel::LefunAiRequestIntoAudioTransmit() { tjd_lefun_ai_request_into_audio_transmit(); }
void TjdUiAppLefunAiModel::LefunAiRequestStartAudioTransmit() { tjd_lefun_ai_request_start_audio_transmit(); }
void TjdUiAppLefunAiModel::LefunAiRequestExitLefunAi() { tjd_lefun_ai_request_exit_lefun_ai(); }
void TjdUiAppLefunAiModel::LefunAiRequestEndAudioTransmit() { tjd_lefun_ai_request_end_audio_transmit(); }
void TjdUiAppLefunAiModel::TransmitAudioData(uint8_t *data, uint16_t len) { tjd_ble_transmit_audio_data(data, len); }
bool TjdUiAppLefunAiModel::GetAiAudioTransFlag() { return tjd_ble_get_ai_audio_trans_flag(); }
void lefun_ai_data_callback(struct data_flow *data)
{
printf("LefunAiModel data_callback");
if (data == nullptr)
return;
if (data->data == nullptr) {
return;
}
data_flow = data;
if (data_flow->data_flow_type == 0) {
story_question_flag = true;
// TjdUiAppLefunAiModel::question.clear();
question = std::string((char *)data_flow->data, data_flow->len);
// TjdUiAppLefunAiModel::question(std::string((char *)data_flow->data));
printf("LefunAiModel data_callback question:%s\n", question.c_str());
} else if (data_flow->data_flow_type == 1) {
story_answer_flag = true;
// TjdUiAppLefunAiModel::answer.clear();
// TjdUiAppLefunAiModel::answer.append(std::string((char *)data_flow->data));
answer += std::string((char *)data_flow->data);
}
GraphicService::GetInstance()->PostGraphicEvent([&]() {
if (story_question_flag && story_answer_flag) {
TjdUiAppLefunAiView::GetInstance()->ShowView(LEFUN_AI_VIEW_ANSWER_VIEW);
}
});
}
static OHOS::GraphicTimer *waiterTimer_ = nullptr; // 运动进行中计时器
std::atomic<bool> waiterTimerStart(false);
static void WaiterTimerCallback(void *arg)
{
printf("LefunAiModel WaiterTimerCallback\n");
if (waiterTimerStart.load()) {
printf("LefunAiModel WaiterTimerCallback start\n");
waiterTimerStart.store(false);
waiterTimer_->Stop();
waiterTimer_->Start();
return;
}
waiterTimerStart.store(false);
if (data_flow->data != NULL) {
free(data_flow->data);
data_flow->data = NULL;
}
data_flow->len = 0;
data_flow->data_flow_id = 0;
data_flow->data_flow_type = 0;
// 保存历史记录到 JSON 文件
GraphicService::GetInstance()->PostGraphicEvent([&]() {
History lefunAiHistory;
TjdUiAppLefunAiModel::GetInstance().loadFromJson(&lefunAiHistory);
// printf("line = %d=======lefunAiHistory=%d==========\n", __LINE__, lefunAiHistory.total_records);
story_answer_flag = false;
TjdUiAppLefunAiModel::GetInstance().addRecord(&lefunAiHistory, question, answer);
// printf("line = %d=========lefunAiHistory=%d==========\n", __LINE__, lefunAiHistory.total_records);
TjdUiAppLefunAiModel::GetInstance().saveToJson(&lefunAiHistory, (const char *)"/system/lefun_ai_record.json");
question.clear();
answer.clear();
});
}
void TjdUiAppLefunAiModel::DeleDataFlow(void)
{
if (data_flow->data != NULL) {
free(data_flow->data);
data_flow->data = NULL;
}
if (data_flow) {
data_flow->len = 0;
data_flow->data_flow_id = 0;
data_flow->data_flow_type = 0;
}
// 保存历史记录到 JSON 文件
History lefunAiHistory;
// printf("line = %d==================\n", __LINE__);
TjdUiAppLefunAiModel::GetInstance().loadFromJson(&lefunAiHistory);
// printf("line = %d=======lefunAiHistory=%d==========\n", __LINE__, lefunAiHistory.total_records);
story_answer_flag = false;
TjdUiAppLefunAiModel::GetInstance().addRecord(&lefunAiHistory, question, answer);
// printf("line = %d=========lefunAiHistory=%d==========\n", __LINE__, lefunAiHistory.total_records);
TjdUiAppLefunAiModel::GetInstance().saveToJson(&lefunAiHistory, (const char *)"/system/lefun_ai_record.json");
question.clear();
answer.clear();
}
void lefun_ai_data_end_callback(struct data_flow *data)
{
printf("LefunAiModel lefun_ai_data_end_callback");
if (data == nullptr)
return;
if (data->data == nullptr) {
return;
}
GraphicService::GetInstance()->PostGraphicEvent([&]() {
data_flow = data;
if (story_question_flag && story_answer_flag) {
TjdUiAppLefunAiView::GetInstance()->ShowView(LEFUN_AI_VIEW_ANSWER_VIEW);
auto AnswerView = LefunAiAnswerView::GetInstance();
int bottom = AnswerView->answerView_.GetRect().GetBottom();
AnswerView->answerView_.ScrollBy(0, -bottom);
printf("LefunAiModel ScrollBy %d\n", -bottom);
}
// if (waiterTimer_ == nullptr) {
// waiterTimer_ = new OHOS::GraphicTimer(5000, WaiterTimerCallback, (void *)data, false);
// waiterTimer_->Start();
// }
});
}
void TjdUiAppLefunAiModel::RegisterLefunAiDataCallback(lefun_ai_data_callback_t callback)
{
register_lefun_ai_data_callback(callback);
}
void TjdUiAppLefunAiModel::UnregisterLefunAiDataCallback() { unregister_lefun_ai_data_callback(); }
void TjdUiAppLefunAiModel::RegisterLefunAiDataEndCallback(lefun_ai_data_callback_t callback)
{
register_lefun_ai_data_end_callback(callback);
}
void TjdUiAppLefunAiModel::UnregisterLefunAiDataEndCallback() { unregister_lefun_ai_data_end_callback(); }
void TjdUiAppLefunAiModel::CloseAutoScreenOff(void)
{
const power_display_svr_api_t *handle = power_display_svr_get_api();
handle->set_screen_set_keepon_timeout(LEFUN_AI_SCREEN_AUTO_OFF_TIME);
}
void TjdUiAppLefunAiModel::OpenAutoScreenOff(void)
{
const power_display_svr_api_t *handle = power_display_svr_get_api();
handle->set_screen_set_keepon_timeout(0);
// handle->set_screen_set_keepon_timeout(sql_setting_get_close_screen_time() * 1000);
}
} // namespace TJD