鸿蒙 WiFi 扫描流程(2)
接着上篇没有记录完的,我们继续梳理,需要上一篇做基础的请看:鸿蒙 WiFi 扫描流程(1)
上一篇我们讲到 scan_service.cpp 里面的 SingleScan 方法,继续这个方法往下看:
// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_scan/scan_service.cppbool ScanService::SingleScan(ScanConfig &scanConfig)
{WIFI_LOGI("Enter ScanService::SingleScan.\n");#ifndef OHOS_ARCH_LITEif (!standByListerner.AllowScan()) {WIFI_LOGE("Scan not allowed when device in standby state.\n");return WIFI_OPT_FAILED;}
#endifGetAllowBandFreqsControlInfo(scanConfig.scanBand, scanConfig.scanFreqs); ---> 哪个Bandif ((scanConfig.scanBand == SCAN_BAND_UNSPECIFIED) && (scanConfig.scanFreqs.empty())) {WIFI_LOGE("Have no allowed band or freq.\n");return false;}InterScanConfig interConfig;interConfig.fullScanFlag = scanConfig.fullScanFlag;interConfig.hiddenNetworkSsid.assign(scanConfig.hiddenNetworkSsid.begin(), scanConfig.hiddenNetworkSsid.end());interConfig.scanStyle = scanConfig.scanStyle;/* Specified frequency */if (scanConfig.scanBand == SCAN_BAND_UNSPECIFIED) {interConfig.scanFreqs.assign(scanConfig.scanFreqs.begin(), scanConfig.scanFreqs.end());/** When band is SCAN_BAND_BOTH_WITH_DFS, need to scan all frequency,* scanFreqs can be empty.*/} else if (scanConfig.scanBand != SCAN_BAND_BOTH_WITH_DFS) {/* Converting frequency bands to frequencies. */if (!GetBandFreqs(scanConfig.scanBand, interConfig.scanFreqs)) {WIFI_LOGE("GetBandFreqs failed.\n");return false;}}/* Save the configuration. */int requestIndex = StoreRequestScanConfig(scanConfig, interConfig);if (requestIndex == MAX_SCAN_CONFIG_STORE_INDEX) {WIFI_LOGE("StoreRequestScanConfig failed.\n");return false;}std::unique_lock<std::mutex> lock(scanConfigMapMutex);if (pScanStateMachine == nullptr) {WIFI_LOGE("pScanStateMachine is null.\n");return false;}/* Construct a message. */ // 去状态机里面处理这个消息InternalMessage *interMessage =pScanStateMachine->CreateMessage(static_cast<int>(CMD_START_COMMON_SCAN), requestIndex);if (interMessage == nullptr) {scanConfigMap.erase(requestIndex);WIFI_LOGE("CreateMessage failed.\n");return false;}if (!AddScanMessageBody(interMessage, interConfig)) {scanConfigMap.erase(requestIndex);MessageManage::GetInstance().ReclaimMsg(interMessage);WIFI_LOGE("AddScanMessageBody failed.\n");return false;}pScanStateMachine->SendMessage(interMessage);return true;
}//foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_scan/scan_state_machine.cpp
bool ScanStateMachine::HardwareReady::ExecuteStateMsg(InternalMessage *msg)
{WIFI_LOGI("ScanStateMachine::HardwareReady::ExecuteStateMsg.\n");if (msg == nullptr) {WIFI_LOGE("msg is null.\n");return true;}switch (msg->GetMessageName()) {case CMD_START_COMMON_SCAN: ---》 处理这个消息pScanStateMachine->CommonScanRequestProcess(msg);return true;case CMD_START_PNO_SCAN:pScanStateMachine->PnoScanRequestProcess(msg);return true;default:return false;}
}
// 发起 CommonScanRequestProcess 流程
void ScanStateMachine::CommonScanRequestProcess(InternalMessage *interMessage)
{WIFI_LOGI("ScanStateMachine::CommonScanRequestProcess.\n");int requestIndex = 0;InterScanConfig scanConfig;if (!GetCommonScanRequestInfo(interMessage, requestIndex, scanConfig)) {ReportCommonScanFailed(requestIndex);return;}if (!VerifyScanStyle(scanConfig.scanStyle)) {WIFI_LOGE("invalid scan type");return;}{std::unique_lock<std::shared_mutex> guard(lock);waitingScans.insert(std::pair<int, InterScanConfig>(requestIndex, scanConfig));}StartNewCommonScan();
}void ScanStateMachine::StartNewCommonScan()
{WIFI_LOGI("Enter ScanStateMachine::StartNewCommonScan.\n");{std::shared_lock<std::shared_mutex> guard(lock);if (waitingScans.size() == 0) {ContinuePnoScanProcess();return;}ClearRunningScanSettings();bool hasFullScan = false;/* Traverse the request list and combine parameters */std::map<int, InterScanConfig>::iterator configIter = waitingScans.begin();for (; configIter != waitingScans.end(); ++configIter) {runningScanSettings.scanStyle = MergeScanStyle(runningScanSettings.scanStyle, configIter->second.scanStyle);std::vector<std::string>::iterator hiddenIter = configIter->second.hiddenNetworkSsid.begin();/* Remove duplicate hidden list */for (; hiddenIter != configIter->second.hiddenNetworkSsid.end(); ++hiddenIter) {if (std::find(runningScanSettings.hiddenNetworkSsid.begin(),runningScanSettings.hiddenNetworkSsid.end(),*hiddenIter) != runningScanSettings.hiddenNetworkSsid.end()) {continue;}runningScanSettings.hiddenNetworkSsid.push_back(*hiddenIter);}if (!hasFullScan) {/* When scanFreqs is empty, it means that scan all frequenties */if (configIter->second.scanFreqs.empty()) {runningScanSettings.scanFreqs.clear();runningFullScanFlag = true;hasFullScan = true;} else {std::vector<int>::iterator freqIter = configIter->second.scanFreqs.begin();/* Repetitions are eliminated */for (; freqIter != configIter->second.scanFreqs.end(); ++freqIter) {if (std::find(runningScanSettings.scanFreqs.begin(),runningScanSettings.scanFreqs.end(),*freqIter) != runningScanSettings.scanFreqs.end()) {continue;}runningScanSettings.scanFreqs.push_back(*freqIter);}}}}}if (!StartSingleCommonScan(runningScanSettings)) { ---》 继续看这个ReportCommonScanFailedAndClear(false);ContinuePnoScanProcess();return;}std::unique_lock<std::shared_mutex> guard(lock);runningScans.swap(waitingScans);waitingScans.clear();SwitchState(commonScanningState);WIFI_LOGI("StartNewCommonScan success.\n");
}bool ScanStateMachine::StartSingleCommonScan(WifiScanParam &scanParam)
{WIFI_LOGI("Enter ScanStateMachine::StartSingleCommonScan.\n");for (auto freqIter = scanParam.scanFreqs.begin(); freqIter != scanParam.scanFreqs.end(); ++freqIter) {WIFI_LOGI("freq is %{public}d.\n", *freqIter);}for (auto hiddenIter = scanParam.hiddenNetworkSsid.begin(); hiddenIter != scanParam.hiddenNetworkSsid.end();++hiddenIter) {WIFI_LOGI("hidden ssid is %{public}s.\n", SsidAnonymize(*hiddenIter).c_str());}WIFI_LOGI("Begin call Scan.\n");WifiErrorNo ret = WifiStaHalInterface::GetInstance().Scan(scanParam); ---> 是不是很熟悉,要通过idl_clientif ((ret != WIFI_IDL_OPT_OK) && (ret != WIFI_IDL_OPT_SCAN_BUSY)) {WIFI_LOGE("WifiStaHalInterface::GetInstance().scan failed.");return false;}WIFI_LOGI("End call Scan.\n");/** Start the timer. If no result is returned for a long time, the scanning* fails*/StartTimer(static_cast<int>(WAIT_SCAN_RESULT_TIMER), MAX_WAIT_SCAN_RESULT_TIME);return true;
}
继续看下代码,WifiStaHalInterface里面的 Scan 方法:
foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/idl_client/wifi_sta_hal_interface.cpp
WifiErrorNo WifiStaHalInterface::Scan(const WifiScanParam &scanParam)
{CHECK_NULL_AND_RETURN(mIdlClient, WIFI_IDL_OPT_FAILED);return mIdlClient->Scan(scanParam);
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/idl_client/wifi_idl_client.cpp
WifiErrorNo WifiIdlClient::Scan(const WifiScanParam &scanParam)
{CHECK_CLIENT_NOT_NULL;ScanSettings settings;if (memset_s(&settings, sizeof(settings), 0, sizeof(settings)) != EOK) {return WIFI_IDL_OPT_FAILED;}bool bfail = false;do {if (scanParam.hiddenNetworkSsid.size() > 0) {settings.hiddenSsidSize = scanParam.hiddenNetworkSsid.size();settings.hiddenSsid = ConVectorToCArrayString(scanParam.hiddenNetworkSsid);if (settings.hiddenSsid == nullptr) {bfail = true;break;}}if (scanParam.scanFreqs.size() > 0) {settings.freqSize = scanParam.scanFreqs.size();settings.freqs = (int *)calloc(settings.freqSize, sizeof(int));if (settings.freqs == nullptr) {bfail = true;break;}for (int i = 0; i < settings.freqSize; ++i) {settings.freqs[i] = scanParam.scanFreqs[i];}}if (scanParam.scanStyle > 0) {settings.scanStyle = scanParam.scanStyle;}} while (0);WifiErrorNo err = WIFI_IDL_OPT_FAILED;if (!bfail) {err = StartScan(&settings); ---> 向 Hal 发起扫描}if (settings.freqs != nullptr) {free(settings.freqs);settings.freqs = nullptr;}if (settings.hiddenSsid != nullptr) {for (int i = 0; i < settings.hiddenSsidSize; ++i) {free(settings.hiddenSsid[i]);settings.hiddenSsid[i] = nullptr;}free(settings.hiddenSsid);settings.hiddenSsid = nullptr;}return err;
}
// 中间省略了 RPC的调用,代码可以看wifi_hal_crpc_server类,处理客户端请求,然后根据请求找到对应的函数,在调用HAL的方法
// 直接看调用的HAL方法:foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_sta_interface.c
WifiErrorNo StartScan(const ScanSettings *settings)
{LOGD("Ready to start scan with param.");
#ifdef HDI_INTERFACE_SUPPORTint ret = HdiStartScan(settings);
#elseWifiWpaStaInterface *pStaIfc = GetWifiStaInterface(0);if (pStaIfc == NULL) {return WIFI_HAL_SUPPLICANT_NOT_INIT;}int ret = pStaIfc->wpaCliCmdScan(pStaIfc, settings); ---> 往supplicant 发送命令if (ret < 0) {LOGE("StartScan failed! ret=%{public}d", ret);return WIFI_HAL_FAILED;}
#endifif (ret == WIFI_HAL_SCAN_BUSY) {LOGD("StartScan return scan busy");return WIFI_HAL_SCAN_BUSY;}LOGD("StartScan successfully!");return WIFI_HAL_SUCCESS;
}// int (*wpaCliCmdScan)(WifiWpaStaInterface *p, const ScanSettings *settings);
// wifi/services/wifi_standard/wifi_hal/wifi_hal_module/wpa_supplicant_hal/wpa_sta_hal/wifi_supplicant_hal.c
static int WpaCliCmdScan(WifiWpaStaInterface *this, const ScanSettings *settings)
{if (this == NULL) {LOGE("WpaCliCmdScan, this is NULL!");return -1;}/* Invalidate expired scan results */WpaCliCmdBssFlush(this);unsigned len = CMD_BUFFER_SIZE;unsigned expectedLen = 0;if (settings != NULL) {expectedLen = AssignCmdLen(this, settings);}if (expectedLen >= len) {len = expectedLen + 1;}char *pcmd = (char *)calloc(len, sizeof(char));if (pcmd == NULL) {LOGE("WpaCliCmdScan, pcmd is NULL!");return -1;}int pos = 0;int res = 0;if (settings != NULL) {if (settings->scanStyle == SCAN_TYPE_PNO && settings->isStartPnoScan) {res = snprintf_s(pcmd, len, len - 1, "IFNAME=%s set pno 1", this->ifname);} else if (settings->scanStyle == SCAN_TYPE_PNO && !settings->isStartPnoScan) {res = snprintf_s(pcmd, len, len - 1, "IFNAME=%s set pno 0", this->ifname);} else {res = snprintf_s(pcmd, len, len - 1, "IFNAME=%s SCAN", this->ifname);}}if (res < 0) {LOGE("WpaCliCmdScan, snprintf_s error!");free(pcmd);return -1;}pos += res;if (settings != NULL && ConcatScanSetting(settings, pcmd + pos, len - pos) < 0) {LOGE("snprintf scan settings error");free(pcmd);return -1;}char buf[REPLY_BUF_SMALL_LENGTH] = {0};if (WpaCliCmd(pcmd, buf, sizeof(buf)) != 0) { ---》 命令下发free(pcmd);return -1;}free(pcmd);if (strncmp(buf, "FAIL-BUSY", strlen("FAIL-BUSY")) == 0) {LOGE("WpaCliCmdScan, WpaCliCmd return FAIL-BUSY!");return FAIL_BUSY;}return 0;
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_module/wpa_supplicant_hal/wifi_wpa_common.c
int WpaCliCmd(const char *cmd, char *buf, size_t bufLen)
{if (cmd == NULL || buf == NULL || bufLen <= 0) {LOGE("WpaCliCmd, invalid parameters!");return -1;}WpaCtrl *ctrl = GetWpaCtrl();if (ctrl == NULL || ctrl->pSend == NULL) {LOGE("WpaCliCmd, ctrl/ctrl->pSend is NULL!");return -1;}size_t len = bufLen - 1;LOGI("wpa_ctrl_request -> cmd: %{private}s", cmd);int ret = wpa_ctrl_request(ctrl->pSend, cmd, strlen(cmd), buf, &len, NULL);if (ret == WPA_CMD_RETURN_TIMEOUT) {LOGE("[%{private}s] command timed out.", cmd);return WPA_CMD_RETURN_TIMEOUT;} else if (ret < 0) {LOGE("[%{private}s] command failed.", cmd);return -1;}buf[len] = '\0';LOGI("wpa_ctrl_request -> buf: %{private}s", buf);if (strncmp(buf, "FAIL\n", strlen("FAIL\n")) == 0 ||strncmp(buf, "UNKNOWN COMMAND\n", strlen("UNKNOWN COMMAND\n")) == 0) {LOGE("%{private}s request success, but response %{public}s", cmd, buf);return -1;}return 0;
}
到此我们就看到命令发送到wpa,然后wpa 收到命令后去做扫描动作,那扫描到结果如何通知上层呢?下一篇我们继续记录。
相关文章:
鸿蒙 WiFi 扫描流程(2)
接着上篇没有记录完的,我们继续梳理,需要上一篇做基础的请看:鸿蒙 WiFi 扫描流程(1) 上一篇我们讲到 scan_service.cpp 里面的 SingleScan 方法,继续这个方法往下看: // foundation/communicat…...

微信小程序(四十)API的封装与调用
注释很详细,直接上代码 上一篇 新增内容: 1.在单独的js文件中写js接口 2.以注册为全局wx的方式调用接口 源码: utils/testAPI.js const testAPI{/*** * param {*} title */simpleToast(title提示){//可传参,默认为‘提示’wx.sho…...

WebSocket+Http实现功能加成
WebSocketHttp实现功能加成 前言 首先,WebSocket和HTTP是两种不同的协议,它们在设计和用途上有一些显著的区别。以下是它们的主要特点和区别: HTTP (HyperText Transfer Protocol): 请求-响应模型: HTTP 是基于请求-响应模型的协…...
go语言实现LRU缓存
go语言实现LRU Cache 题目描述详细代码 题目描述 设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最…...
git的奇特知识点
展示帮助信息 git help -gThe common Git guides are:attributes Defining attributes per pathcli Git command-line interface and conventionscore-tutorial A Git core tutorial for developerscvs-migration Git for CVS usersdiff…...

按键扫描16Hz-单片机通用模板
按键扫描16Hz-单片机通用模板 一、按键扫描的原理1、直接检测高低电平类型2、矩阵扫描类型3、ADC检测类型二、key.c的实现1、void keyScan(void) 按键扫描函数①void FHiKey(void) 按键按下功能②void FSameKey(void) 按键长按功能③void FLowKey(void) 按键释放功能三、key.h的…...
在容器镜像中为了安全为什么要删除 setuid 和 setgid?
在容器镜像中删除 setuid(set user ID)和 setgid(set group ID)权限通常是出于安全考虑。这两个权限位允许进程在执行时以文件所有者或文件所属组的身份运行,而不是以调用进程的用户身份运行。 删除 setuid 和 setgid…...

Flink 动态表 (Dynamic Table) 解读
博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,…...

【原创 附源码】Flutter海外登录--Google登录最详细流程
最近接触了几个海外登录的平台,踩了很多坑,也总结了很多东西,决定记录下来给路过的兄弟坐个参考,也留着以后留着回顾。更新时间为2024年2月8日,后续集成方式可能会有变动,所以目前的集成流程仅供参考&#…...

第70讲axios后端请求工具类封装
axios工具类封装: // 引入axios import axios from axios;// 创建axios实例 const httpService axios.create({// url前缀-http:xxx.xxx// baseURL: process.env.BASE_API, // 需自定义baseURL:http://localhost:80/,// 请求超时时间timeout: 3000 // 需自定义 })…...

【数学建模】【2024年】【第40届】【MCM/ICM】【F题 减少非法野生动物贸易】【解题思路】
一、题目 (一) 赛题原文 2024 ICM Problem F: Reducing Illegal Wildlife Trade Illegal wildlife trade negatively impacts our environment and threatens global biodiversity. It is estimated to involve up to 26.5 billion US dollars per y…...

第3节、电机定速转动【51单片机+L298N步进电机系列教程】
↑↑↑点击上方【目录】,查看本系列全部文章 摘要:本节介绍用定时器定时的方式,精准控制脉冲时间,从而控制步进电机速度。 一、计算过程 电机每一步的角速度等于走这一步所花费的时间,走一步角度等于步距角ÿ…...

【51单片机】LCD1602(可视化液晶屏)调试工具的使用
前言 大家好吖,欢迎来到 YY 滴 单片机系列 ,热烈欢迎! 本章主要内容面向接触过单片机的老铁 主要内容含: 欢迎订阅 YY滴C专栏!更多干货持续更新!以下是传送门! YY的《C》专栏YY的《C11》专栏YY…...

Netty应用(四) 之 Reactor模型 零拷贝
目录 6.Reactor模型 6.1 单线程Reactor 6.2 主从多线程Reactor (主--->Boss | 从--->Worker | 一主多从机制) 7.扩展与补充 8.Reactor模型的实现 8.1 多线程Reactor模型的实现(一个Boss线程,一个Worker线程) 8.2 多线程Reactor模…...

Huggingface上传模型
Huggingface上传自己的模型 参考 https://juejin.cn/post/7081452948550746148https://huggingface.co/blog/password-git-deprecationAdding your model to the Hugging Face Hub, huggingface.co/docs/hub/ad…Welcome,huggingface.co/welcome三句指…...
kyuubi 接入starrocks | doris
kyuubi 接入starrocks 一、环境 Hadoop集群 组件版本Hadoop3.1.1spark3.Xzookeeper3.XHive3.X kyuubi 版本 1.7.1 starrocks 2.X 已将kyuubi部署到yarn上,并且接入了spark3引擎,并通过Ambari进行kyuubi组件的管理,下面步骤为新增对sta…...

notepad++成功安装后默认显示英文怎么设置中文界面?
前几天使用电脑华为管家清理电脑后,发现一直使用的notepad软件变回了英文界面,跟刚成功安装的时候一样,那么应该怎么设置为中文界面呢?具体操作如下: 1、打开notepad软件,点击菜单栏“Settings – Prefere…...

HiveSQL——连续增长问题
注:参考文章: SQL连续增长问题--HQL面试题35_sql判断一个列是否连续增长-CSDN博客文章浏览阅读2.6k次,点赞6次,收藏30次。目录0 需求分析1 数据准备3 小结0 需求分析假设我们有一张订单表shop_order shop_id,order_id,order_time…...

使用cocos2d-console初始化一个项目
先下载好cocos2d-x的源码包 地址 https://www.cocos.com/cocos2dx-download 这里使用的版本是 自己的电脑要先装好python27 用python安装cocos2d-console 看到项目中有个setup.py的一个文件 python setup.py 用上面的命令执行一下。 如果执行正常的话回出现上面的图 然后…...

VitePress-13- 配置-title的作用详解
作用描述 1、title 是当前站点的标题;2、默认值是 :VitePress;3、当使用默认主题时,会直接展示在 页面的【导航条】中;4、一个特殊的作用 : 会作为单个页面的默认标题后缀!除非又指定了【title…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...

数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...