小智源码分析——音频部分(二)

一、利用创建好的对象来调用音频服务 

上周从上图的getaudiocode()方法进去感受了一下底层小智的构造如何实现。所以用一个codec来接收我们所构造的音频对象。

下来是用构造好的音频对象来调用音频初始化服务Initialize,因为启动函数Application函数的类中有audio_servicez_所以可以进行调用。

这段初始化代码的核心作用是:

1绑定并启动音频编解码器

2配置音频数据流的格式和处理流程

3按需初始化音频处理器和唤醒词检测模块

4设置好各类回调,保证音频事件能及时通知到主程序

5创建定时器,自动管理音频硬件电源

void AudioService::Initialize(AudioCodec* codec) {// 保存传入的音频编解码器指针codec_ = codec;// 启动音频编解码器,准备采集和播放codec_->Start();/* 初始化 Opus 解码器和编码器 */// 创建 Opus 解码器,采样率与输出一致,单声道,帧长为 OPUS_FRAME_DURATION_MSopus_decoder_ = std::make_unique<OpusDecoderWrapper>(codec->output_sample_rate(), 1, OPUS_FRAME_DURATION_MS);// 创建 Opus 编码器,采样率固定为 16kHz,单声道,帧长为 OPUS_FRAME_DURATION_MSopus_encoder_ = std::make_unique<OpusEncoderWrapper>(16000, 1, OPUS_FRAME_DURATION_MS);// 设置编码复杂度为最低,节省算力opus_encoder_->SetComplexity(0);// 如果输入采样率不是 16kHz,则配置重采样器,将输入音频转换为 16kHzif (codec->input_sample_rate() != 16000) {input_resampler_.Configure(codec->input_sample_rate(), 16000);reference_resampler_.Configure(codec->input_sample_rate(), 16000);}// 根据编译配置选择不同的音频处理器(如带有回声消除的AFE,或无处理的空实现)
#if CONFIG_USE_AUDIO_PROCESSORaudio_processor_ = std::make_unique<AfeAudioProcessor>();
#elseaudio_processor_ = std::make_unique<NoAudioProcessor>();
#endif// 根据编译配置选择不同的唤醒词检测算法
#if CONFIG_USE_AFE_WAKE_WORDwake_word_ = std::make_unique<AfeWakeWord>();
#elif CONFIG_USE_ESP_WAKE_WORDwake_word_ = std::make_unique<EspWakeWord>();
#elif CONFIG_USE_CUSTOM_WAKE_WORDwake_word_ = std::make_unique<CustomWakeWord>();
#elsewake_word_ = nullptr;
#endif// 设置音频处理器的输出回调,当有处理好的音频输出时,推入编码队列audio_processor_->OnOutput([this](std::vector<int16_t>&& data) {PushTaskToEncodeQueue(kAudioTaskTypeEncodeToSendQueue, std::move(data));});// 设置语音活动检测(VAD)回调,检测到说话状态变化时,更新状态并通知外部audio_processor_->OnVadStateChange([this](bool speaking) {voice_detected_ = speaking;if (callbacks_.on_vad_change) {callbacks_.on_vad_change(speaking);}});// 如果启用了唤醒词检测,设置唤醒词检测回调,检测到唤醒词时通知外部if (wake_word_) {wake_word_->OnWakeWordDetected([this](const std::string& wake_word) {if (callbacks_.on_wake_word_detected) {callbacks_.on_wake_word_detected(wake_word);}});}// 创建音频电源管理定时器,定期检查音频输入/输出是否需要关闭以省电esp_timer_create_args_t audio_power_timer_args = {.callback = [](void* arg) {AudioService* audio_service = (AudioService*)arg;audio_service->CheckAndUpdateAudioPowerState();},.arg = this,.dispatch_method = ESP_TIMER_TASK,.name = "audio_power_timer",.skip_unhandled_events = true,};esp_timer_create(&audio_power_timer_args, &audio_power_timer_);
}

二、启动音频服务

经过上部分的初始化,配置好了音频的编解码器,以及处理时对于音频的要求(不符合要求的要重新采样为符合要求的格式),还包括唤醒词的检测、提取和回调。

启动流程(Start)

1标记服务未停止

service_stopped_ = false;

让各任务知道服务正在运行。

2清除音频相关事件位

xEventGroupClearBits(...)

确保音频输入、唤醒词、音频处理等任务可以正常启动。

3启动音频电源管理定时器

esp_timer_start_periodic(...)

每秒检查一次音频硬件的电源状态,自动省电。

4启动音频输入任务

xTaskCreatePinnedToCore 或 xTaskCreate

创建音频采集任务,负责从麦克风采集音频数据。

5启动音频输出任务

xTaskCreate

创建音频播放任务,负责将音频数据输出到扬声器。

6启动 Opus 编解码任务

xTaskCreate

创建音频编解码任务,负责音频数据的编码(发送)和解码(播放)。

void AudioService::Start() {// 标记服务未停止service_stopped_ = false;// 清除音频相关的事件位,确保任务可以正常启动xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING | AS_EVENT_WAKE_WORD_RUNNING | AS_EVENT_AUDIO_PROCESSOR_RUNNING);// 启动音频电源管理定时器,每秒检查一次音频硬件电源状态esp_timer_start_periodic(audio_power_timer_, 1000000);/* 启动音频输入任务 */
#if CONFIG_USE_AUDIO_PROCESSOR// 如果使用音频处理器,任务绑定到指定内核xTaskCreatePinnedToCore([](void* arg) {AudioService* audio_service = (AudioService*)arg;audio_service->AudioInputTask();vTaskDelete(NULL);}, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_, 1);
#else// 不使用音频处理器,普通方式创建任务xTaskCreate([](void* arg) {AudioService* audio_service = (AudioService*)arg;audio_service->AudioInputTask();vTaskDelete(NULL);}, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_);
#endif/* 启动音频输出任务 */xTaskCreate([](void* arg) {AudioService* audio_service = (AudioService*)arg;audio_service->AudioOutputTask();vTaskDelete(NULL);}, "audio_output", 4096, this, 3, &audio_output_task_handle_);/* 启动 Opus 编解码任务 */xTaskCreate([](void* arg) {AudioService* audio_service = (AudioService*)arg;audio_service->OpusCodecTask();vTaskDelete(NULL);}, "opus_codec", 4096 * 7, this, 2, &opus_codec_task_handle_);
}

三、音频回调服务

下来回到Application函数内,下一步执行下图这一模块: 

首先定义一个callbacks对象,他的类型如下:

 AudioServiceCallbacks 是一个回调函数集合,用于让外部(比如主应用 Application)能够“订阅”音频服务(AudioService)中的各种事件。当音频服务内部发生特定事件时,会自动调用这些回调,通知外部进行相应处理。

struct AudioServiceCallbacks {std::function<void(void)> on_send_queue_available;std::function<void(const std::string&)> on_wake_word_detected;std::function<void(bool)> on_vad_change;std::function<void(void)> on_audio_testing_queue_full;
};

让主程序通过事件组机制,能够及时响应音频服务中的关键事件,实现音频事件的异步通知和处理。 

 

每个成员的含义

  • on_send_queue_available

类型:std::function<void(void)>

说明:当音频发送队列有可用数据时触发。比如可以通知主程序“可以发送音频数据到服务器了”。

  • on_wake_word_detected

类型:std::function<void(const std::string&)>

说明:当检测到唤醒词(如“小智”)时触发。参数是检测到的唤醒词内容。

  • on_vad_change

类型:std::function<void(bool)>

说明:当语音活动检测(VAD)状态发生变化时触发。参数 bool 表示当前是否有人在说话(true=正在说话,false=静音)。

  • on_audio_testing_queue_full

类型:std::function<void(void)>

说明:当音频测试队列已满时触发。一般用于调试或测试场景。

异步和函数回调的区别?

方面异步执行自动回调
是否并发是,任务后台运行不一定,回调是响应机制
主体是谁程序发起的异步任务异步任务完成后执行的函数
控制权主程序不阻塞,控制权立即返回控制权在回调被触发时才回到你手里
是否依赖异步异步通常搭配回调使用回调常用在异步任务,但也可用于同步场景
举个例子setTimeout()不会阻塞主线程setTimeout(fn, 1000) 中的 fn 是回调

四、音频服务具体功能

分别了解下列三个核心任务函数:

  • AudioInputTask():音频采集
  • AudioOutputTask():音频播放
  • OpusCodecTask():音频编解码

// 音频输入任务,运行在一个 FreeRTOS 任务中
void AudioService::AudioInputTask() {while (true) {// 等待音频相关事件触发:测试模式、唤醒词检测、通用音频处理EventBits_t bits = xEventGroupWaitBits(event_group_,AS_EVENT_AUDIO_TESTING_RUNNING |AS_EVENT_WAKE_WORD_RUNNING |AS_EVENT_AUDIO_PROCESSOR_RUNNING,pdFALSE,      // 不清除标志位pdFALSE,      // 任意一个事件即可返回portMAX_DELAY // 无限等待);// 如果服务已经停止,则退出任务if (service_stopped_) {break;}// 若麦克风需要预热,延迟一段时间后继续下一轮循环if (audio_input_need_warmup_) {audio_input_need_warmup_ = false;vTaskDelay(pdMS_TO_TICKS(120)); // 延迟 120mscontinue;}/** ==========================*  音频测试处理逻辑(如按下 BOOT 录音)*  ========================== */if (bits & AS_EVENT_AUDIO_TESTING_RUNNING) {// 判断测试队列是否已满(按最大时长判断)if (audio_testing_queue_.size() >= AUDIO_TESTING_MAX_DURATION_MS / OPUS_FRAME_DURATION_MS) {ESP_LOGW(TAG, "Audio testing queue is full, stopping audio testing");EnableAudioTesting(false); // 自动关闭测试continue;}// 准备读取一帧音频数据(例如 20ms × 16000Hz)std::vector<int16_t> data;int samples = OPUS_FRAME_DURATION_MS * 16000 / 1000;// 如果成功读取音频数据if (ReadAudioData(data, 16000, samples)) {// 若为双声道,仅保留左声道数据(变为单声道)if (codec_->input_channels() == 2) {auto mono_data = std::vector<int16_t>(data.size() / 2);for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {mono_data[i] = data[j];}data = std::move(mono_data);}// 推送数据到测试编码队列PushTaskToEncodeQueue(kAudioTaskTypeEncodeToTestingQueue, std::move(data));continue; // 当前处理完毕,回到等待下一次事件}}/** ==========================*  唤醒词检测处理逻辑*  ========================== */if (bits & AS_EVENT_WAKE_WORD_RUNNING) {std::vector<int16_t> data;int samples = wake_word_->GetFeedSize(); // 获取所需帧长度// 若帧长度有效且成功读取数据if (samples > 0 && ReadAudioData(data, 16000, samples)) {wake_word_->Feed(data); // 投喂唤醒词检测器continue;}}/** ==========================*  通用音频处理逻辑*  ========================== */if (bits & AS_EVENT_AUDIO_PROCESSOR_RUNNING) {std::vector<int16_t> data;int samples = audio_processor_->GetFeedSize(); // 获取处理器需要的数据大小// 若帧有效且数据读取成功if (samples > 0 && ReadAudioData(data, 16000, samples)) {audio_processor_->Feed(std::move(data)); // 投喂音频处理器continue;}}// 如果没有任何已知事件被处理到,这通常是逻辑错误ESP_LOGE(TAG, "Should not be here, bits: %lx", bits);break; // 退出任务}// 最后,任务退出时打印警告日志ESP_LOGW(TAG, "Audio input task stopped");
}
void AudioService::AudioInputTask() {while (true) {EventBits_t bits = xEventGroupWaitBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING |AS_EVENT_WAKE_WORD_RUNNING | AS_EVENT_AUDIO_PROCESSOR_RUNNING,pdFALSE, pdFALSE, portMAX_DELAY);if (service_stopped_) {break;}if (audio_input_need_warmup_) {audio_input_need_warmup_ = false;vTaskDelay(pdMS_TO_TICKS(120));continue;}/* Used for audio testing in NetworkConfiguring mode by clicking the BOOT button */if (bits & AS_EVENT_AUDIO_TESTING_RUNNING) {if (audio_testing_queue_.size() >= AUDIO_TESTING_MAX_DURATION_MS / OPUS_FRAME_DURATION_MS) {ESP_LOGW(TAG, "Audio testing queue is full, stopping audio testing");EnableAudioTesting(false);continue;}std::vector<int16_t> data;int samples = OPUS_FRAME_DURATION_MS * 16000 / 1000;if (ReadAudioData(data, 16000, samples)) {// If input channels is 2, we need to fetch the left channel dataif (codec_->input_channels() == 2) {auto mono_data = std::vector<int16_t>(data.size() / 2);for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {mono_data[i] = data[j];}data = std::move(mono_data);}PushTaskToEncodeQueue(kAudioTaskTypeEncodeToTestingQueue, std::move(data));continue;}}/* Feed the wake word */if (bits & AS_EVENT_WAKE_WORD_RUNNING) {std::vector<int16_t> data;int samples = wake_word_->GetFeedSize();if (samples > 0) {if (ReadAudioData(data, 16000, samples)) {wake_word_->Feed(data);continue;}}}/* Feed the audio processor */if (bits & AS_EVENT_AUDIO_PROCESSOR_RUNNING) {std::vector<int16_t> data;int samples = audio_processor_->GetFeedSize();if (samples > 0) {if (ReadAudioData(data, 16000, samples)) {audio_processor_->Feed(std::move(data));continue;}}}ESP_LOGE(TAG, "Should not be here, bits: %lx", bits);break;}ESP_LOGW(TAG, "Audio input task stopped");
}

 

void AudioService::AudioOutputTask() {while (true) {// 加锁等待播放队列非空或服务停止信号std::unique_lock<std::mutex> lock(audio_queue_mutex_);// 如果队列为空且服务未停止,则阻塞等待条件变量触发audio_queue_cv_.wait(lock, [this]() { return !audio_playback_queue_.empty() || service_stopped_; });// 如果检测到服务已经停止,则退出任务if (service_stopped_) {break;}// 从播放队列取出一个音频任务(前移出队)auto task = std::move(audio_playback_queue_.front());audio_playback_queue_.pop_front();// 通知等待的线程队列已发生变化(唤醒可能的生产者)audio_queue_cv_.notify_all();// 解锁互斥量,开始进行播放处理lock.unlock();// 如果音频输出尚未启用,则启用输出并启动功耗监测定时器if (!codec_->output_enabled()) {codec_->EnableOutput(true);esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000);}// 将 PCM 数据输出到音频设备codec_->OutputData(task->pcm);// 更新时间戳记录为最近一次输出时间last_output_time_ = std::chrono::steady_clock::now();// 播放计数器 +1,用于调试/统计debug_statistics_.playback_count++;#if CONFIG_USE_SERVER_AEC// 若启用了服务器端 AEC,并且任务中包含有效时间戳,则记录该时间戳if (task->timestamp > 0) {lock.lock(); // 重新加锁以保护 timestamp_queue_timestamp_queue_.push_back(task->timestamp);}#endif}// 最后,任务退出时打印日志ESP_LOGW(TAG, "Audio output task stopped");
}
void AudioService::OpusCodecTask() {while (true) {// 加锁并等待条件满足:// - 服务已停止// - 编码队列非空 且 发送队列未满// - 解码队列非空 且 播放队列未满std::unique_lock<std::mutex> lock(audio_queue_mutex_);audio_queue_cv_.wait(lock, [this]() {return service_stopped_ ||(!audio_encode_queue_.empty() && audio_send_queue_.size() < MAX_SEND_PACKETS_IN_QUEUE) ||(!audio_decode_queue_.empty() && audio_playback_queue_.size() < MAX_PLAYBACK_TASKS_IN_QUEUE);});// 若服务已停止,则退出任务if (service_stopped_) {break;}/** ========================* 解码逻辑* ======================== */if (!audio_decode_queue_.empty() && audio_playback_queue_.size() < MAX_PLAYBACK_TASKS_IN_QUEUE) {// 取出一个待解码数据包auto packet = std::move(audio_decode_queue_.front());audio_decode_queue_.pop_front();audio_queue_cv_.notify_all();lock.unlock();  // 解锁以便其他线程访问队列// 构造新的播放任务auto task = std::make_unique<AudioTask>();task->type = kAudioTaskTypeDecodeToPlaybackQueue;task->timestamp = packet->timestamp;// 设置解码参数SetDecodeSampleRate(packet->sample_rate, packet->frame_duration);// 解码数据if (opus_decoder_->Decode(std::move(packet->payload), task->pcm)) {// 如果解码后的采样率不一致,则重采样if (opus_decoder_->sample_rate() != codec_->output_sample_rate()) {int target_size = output_resampler_.GetOutputSamples(task->pcm.size());std::vector<int16_t> resampled(target_size);output_resampler_.Process(task->pcm.data(), task->pcm.size(), resampled.data());task->pcm = std::move(resampled);}// 加锁并推送到播放队列lock.lock();audio_playback_queue_.push_back(std::move(task));audio_queue_cv_.notify_all();} else {// 解码失败ESP_LOGE(TAG, "Failed to decode audio");lock.lock();}debug_statistics_.decode_count++;}/** ========================* 编码逻辑* ======================== */if (!audio_encode_queue_.empty() && audio_send_queue_.size() < MAX_SEND_PACKETS_IN_QUEUE) {auto task = std::move(audio_encode_queue_.front());audio_encode_queue_.pop_front();audio_queue_cv_.notify_all();lock.unlock(); // 解锁以进行编码// 构建音频流数据包auto packet = std::make_unique<AudioStreamPacket>();packet->frame_duration = OPUS_FRAME_DURATION_MS;packet->sample_rate = 16000;packet->timestamp = task->timestamp;// 编码 PCM 数据if (!opus_encoder_->Encode(std::move(task->pcm), packet->payload)) {ESP_LOGE(TAG, "Failed to encode audio");continue;}// 根据任务类型,推送到不同队列if (task->type == kAudioTaskTypeEncodeToSendQueue) {{std::lock_guard<std::mutex> lock(audio_queue_mutex_);audio_send_queue_.push_back(std::move(packet));}// 通知有新的可发送数据if (callbacks_.on_send_queue_available) {callbacks_.on_send_queue_available();}} else if (task->type == kAudioTaskTypeEncodeToTestingQueue) {std::lock_guard<std::mutex> lock(audio_queue_mutex_);audio_testing_queue_.push_back(std::move(packet));}debug_statistics_.encode_count++;lock.lock(); // 重新加锁以进入下一轮循环}}// 任务退出时记录日志ESP_LOGW(TAG, "Opus codec task stopped");
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90971.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90971.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

菜鸟的C#学习(四)

文章目录一、格式说明符1.1、数字格式说明符&#xff08;适用于数值类型&#xff1a;int, double, decimal 等&#xff09;1. 标准数字格式2. 自定义数字格式1.2、日期时间格式说明符&#xff08;适用于 DateTime, DateTimeOffset&#xff09;1. 标准日期时间格式2. 自定义日期…

基于黑马教程——微服务架构解析(二)

本篇文章基于黑马程序员的微服务课程内容&#xff0c;结合个人学习过程中的理解与思考进行整理。本节将围绕以下几个问题展开&#xff1a;什么是网关和配置管理前面那篇文章&#xff0c;我们了解如何把一个单体的项目拆成分布式微服务项目&#xff0c;并且讲解一下各个服务之间…

Text2SQL智能问答系统开发(一)

开发一个面向企业的chatBI工作流 已完成 基础 Text2SQL 功能实现 实现用户输入自然语言问题后&#xff0c;系统能够自动生成 SQL 并执行返回结果。用户交互优化 支持用户通过补充信息对查询进行调整&#xff0c;提升易用性。模糊时间处理机制 对“最近”“近期”等模糊时间关…

Python HTML模块详解:从基础到实战

一、模块体系全景图 Python生态中处理HTML的工具可分为三大层级&#xff1a; 标准库基础层&#xff1a;html模块 html.parser第三方增强层&#xff1a;BeautifulSoup&#xff08;搭配解析器&#xff09;专业级工具层&#xff1a;lxml requests-html 二、标准库核心模块详解…

PyTorch常用Tensor形状变换函数详解

PyTorch常用Tensor形状变换函数详解 在PyTorch中&#xff0c;对张量&#xff08;Tensor&#xff09;进行形状变换是深度学习模型构建中不可或缺的一环。无论是为了匹配网络层的输入要求&#xff0c;还是为了进行数据预处理和维度调整&#xff0c;都需要灵活运用各种形状变换函数…

自主智能Agent如何重塑工作流自动化:技术、经济与未来展望

自主智能Agent的崛起与工作流自动化的范式革命2025年7月&#xff0c;当OpenAI向付费用户推出具备网页浏览和代码执行能力的ChatGPT Agent时&#xff0c;工作流自动化领域迎来了一场静默但彻底的革命。这款不再满足于简单问答的智能体&#xff0c;在一个安全的虚拟计算机环境中运…

技术架构、行业应用、工具链整合、挑战应对及未来趋势五大模块,引用多个权威来源数据与开源项目实现细节。

以下是一份关于AI技术落地的实战经验总结报告&#xff0c;结合代码示例、可视化图表与行业案例&#xff0c;内容分为技术架构、行业应用、工具链整合、挑战应对及未来趋势五大模块&#xff0c;引用多个权威来源数据与开源项目实现细节。AI技术落地实战指南&#xff1a;从架构设…

第 9 篇:神经网络初探——当AI拥有了“大脑”,世界从此不同

《人工智能AI之机器学习基石》系列⑨ 专栏核心理念: 用通俗语言讲清楚机器学习的核心原理,强调“洞察 + 技术理解 + 应用连接”,构建一个完整的、富有启发性的知识体系。

音频焦点 Android Audio Focus 进阶

旧焦点处理 示例调用链: requestAudioFocus() → propagateFocusLossFromGain_syncAf() → handleFocusLossFromGain()。 系统事件(如来电)→ 强制焦点变化 → handleFocusLossFromGain()。 函数 propagateFocusLossFromGain_syncAf 焦点持有者发生的焦点丢失通知 主要功能…

MFC UI对话框

文章目录对话框模态对话框创建销毁关闭CDialog::OnCancel()EndDialog()CDialog::DestroyWindow()非模态对话框创建销毁关闭delete this对话框 模态对话框 ​​阻塞父窗口​​&#xff0c;强制用户先处理对话框。关闭前父窗口无法响应事件。 创建 推荐&#xff1a;非指针方式…

RabbitMQ--@RabbitListener及@RabbitHandle

两者区别 在 Spring AMQP 中&#xff0c;RabbitListener 和 RabbitHandler 是处理 RabbitMQ 消息的核心注解&#xff0c;但职责和使用场景完全不同。以下从 定义、区别、场景、示例 逐层解析&#xff1a;一、核心定义1. RabbitListener作用&#xff1a;标记 方法或类 为 Ra…

【基于CKF的IMM】MATLAB例程,CV和CT两个模型下的IMM,二维,滤波使用CKF(容积卡尔曼滤波),附下载链接

本程序实现了基于交互多模型&#xff08;IMM&#xff09;容积卡尔曼滤波&#xff08;CKF&#xff09;的多模型融合定位方法&#xff0c;并与纯CV−CKFCV-CKFCV−CKF&#xff08;匀速模型&#xff09;和CT−CKFCT-CKFCT−CKF&#xff08;匀角速度转弯模型&#xff09;方法对比。…

AI资讯日报 - 2025年07月28日

AI资讯日报 | 2025年07月28日 周一 今日核心要点 精华提炼 技术突破 • 腾讯混元&#xff1a;开源发布、3D技术 • 书生Intern&#xff1a;开源发布、多模态 企业动态 • AI工具集&#xff1a;协同创作、视频生成 数据概览分类数量重点关注技术突破2 条开源发布、3D技术企业动态…

大语言模型 LLM 通过 Excel 知识库 增强日志分析,根因分析能力的技术方案(1):总体介绍

文章大纲 1. 核心目标 2. 系统总体架构 3. Google Cloud 端到端方案(含无 RAG & RAG 双模式) 3.1 无 RAG:Function-Calling 查表模式 3.2 RAG:托管式向量检索 4. 开源轻量级方案 5. 数字孪生联合验证(实验性) 6. 知识图谱增强(Neo4j) 7. 监控与持续优化(CometLLM)…

Deepseek + browser-use 轻松实现浏览器自动化

在数字化时代&#xff0c;浏览器应用广泛&#xff0c;浏览器自动化可大幅提升效率。Deepseek 是强大的智能语言模型&#xff0c;能精准解析复杂指令&#xff0c;browser - use 是专注浏览器操作的工具&#xff0c;提供丰富 API 接口&#xff0c;支持主流浏览器的各类自动化操作…

开疆智能ModbusTCP转Profient网关连接西门子PLC与川崎机器人配置案例

本案例是西门子PLC与川崎机器人通过Profient转ModbusTCP网关进行通讯转换的配置案例&#xff0c;西门子作为profinet主站&#xff0c;机器人作为ModbusTCP服务器。配置过程&#xff1a;机器人配置川崎机器人控制器提供了RS232、以太网的通信接口&#xff0c;同时也可通过加装选…

Docker多主机网络连接:实现跨主机通信

Docker 是一种流行的容器化平台&#xff0c;它可以帮助开发人员更方便地构建、发布和管理应用程序。在 Docker 中&#xff0c;容器是独立运行的应用程序包装&#xff0c;包含了运行所需的所有文件、库和环境变量。Docker 提供了多种网络连接方式&#xff0c;使得容器之间可以进…

OSPF笔记

一、OSPF基础1、技术背景&#xff08;RIP中存在的问题&#xff09;RIP中存在最大跳数为15的限制&#xff0c;不能适应大规模组网周期性发送全部路由信息&#xff0c;占用大量的带宽资源路由收敛速度慢以跳数作为度量值存在路由环路可能性每隔30秒更新2、OSPF协议特点没有跳数限…

kotlin基础【3】

Kotlin Playground: Edit, Run, Share Kotlin Code Online 资料&#xff1a;kotlin实战 第一章 data class Person(val name: String,val age:Int?null)//允许接受以age为空&#xff0c;当为空将它赋值为null,如果不这么写直接写age:Int?是否可以fun main(args:Array<St…

Java-数构二叉树

1.树 1.1概念 树是一种非线性的数据结构&#xff0c;它是由n个有限节点组成一个具有层次关系。这种结构有以下特点&#xff1a; 一个特殊的结点&#xff0c;称为根节点&#xff0c;根节点没有前驱节点除根节点以外&#xff0c;其余节点分成M个互不相交的集合。每个集合又是一…