OpenHarmony 3GPP协议开发深度剖析——一文读懂RIL

市面上关于终端(手机)操作系统在 3GPP 协议开发的内容太少了,即使 Android 相关的学习文档都很少,Android 协议开发书籍我是没有见过的。可能是市场需求的缘故吧,现在市场上还是前后端软件开发从业人员最多,包括我自己。

基于我曾经也在某手机协议开发团队干过一段时间,协议的 AP 侧和 CP 侧开发都整过,于是想尝试下基于 OpenAtom OpenHarmony(以下简称“OpenHarmony”)源码写点内容,帮助大家了解下协议开发领域,尽可能将 3gpp 协议内容与 OpenHarmony 电话子系统模块进行结合讲解。据我所知,现在终端协议开发非常缺人。首先声明我不是协议专家,我也离开该领域有五六年了,如有错误,欢迎指正。

提到终端协议开发,我首先想到的就是** RIL **了。

专有名词

CP: Communication Processor(通信处理器),我一般就简单理解为 modem 侧,也可以理解为底层协议,这部分由各个 modem 芯片厂商完成(比如海思、高通)。

AP: Application Processor(应用处理器),通常就是指的手机终端,我一般就简单理解为上层协议,主要由操作系统 Telephony 服务来进行处理。

RIL: Radio Interface Layer(无线电接口层),我一般就简单理解为硬件抽象层,即 AP 侧将通信请求传给 CP 侧的中间层。

AT指令: AT 指令是应用于终端设备与 PC 应用之间的连接与通信的指令。

设计思想

常规的 Modem 开发与调试可以使用 AT 指令来进行操作,而各家的 Modem 芯片的 AT 指令都会有各自的差异。因此手机终端厂商为了能在各种不同型号的产品中集成不同 modem 芯片,需要进行解耦设计来屏蔽各家 AT 指令的差异。

于是 OpenHarmony 采用 RIL 对 Modem 进行 HAL(硬件抽象),作为系统与 Modem 之间的通信桥梁,为 AP 侧提供控制 Modem 的接口,各 Modem 厂商则负责提供对应于 AT 命令的 Vender RIL(这些一般为封装好的 so 库),从而实现操作系统与 Modem 间的解耦。

OpenHarmony RIL架构

**框架层:**Telephony Service,电话子系统核心服务模块,主要功能是初始化 RIL 管理、SIM 卡和搜网模块。对应 OpenHarmony 的源码仓库 OpenHarmony / telephony_core_service。这个模块也是非常重要的一个模块,后期单独再做详细解读。

**硬件抽象层:**即我们要讲的 RIL,对应 OpenHarmony 的源码仓库 OpenHarmony / telephony_ril_adapter。RIL Adapter 模块主要包括厂商库加载,业务接口实现以及事件调度管理。主要用于屏蔽不同 modem 厂商硬件差异,为上层提供统一的接口,通过注册 HDF 服务与上层接口通讯。

**芯片层:**Modem 芯片相关代码,即 CP 侧,这些代码各个 Modem 厂商是不开放的,不出现在 OpenHarmony 中。

硬件抽象层

硬件抽象层又被划分为了 hril_hdf 层、hril 层和 venderlib 层。

**hril_hdf层:**HDF 服务,基于 OpenHarmony HDF 框架,提供 hril 层与 Telephony Service 层进行通讯。

**hril 层:**hril 层的各个业务模块接口实现,比如通话、短彩信、数据业务等。

**vendorlib层:**各 Modem 厂商提供的对应于 AT 命令库,各个厂商可以出于代码闭源政策,在这里以 so 库形式提供。目前源码仓中已经提供了一套提供代码的 AT 命令操作,至于这个是针对哪个型号 modem 芯片的,我后续了解清楚再补充。

下面是 ril_adapter 仓的源码结构:

base/telephony/ril_adapter
├── figures                             # readme资源文件
├── frameworks
│   ├── BUILD.gn
│   └── src                             # 序列化文件
├── interfaces                          # 对应提供上层各业务内部接口
│   └── innerkits
├── services                            # 服务
│   ├── hril                            # hril层的各个业务模块接口实现
│   ├── hril_hdf                        # HDF服务
│   └── vendor                          # 厂商库文件
└── test                                # 测试代码
    ├── BUILD.gn
    ├── mock
    └── unittest                        # 单元测试代码

核心业务逻辑梳理

本文解读 RIL 层很小一部分代码,RIL 是如何通过 HDF 与 Telephony 连接上的,以后更加完整的逻辑梳理会配上时序图讲解,会更加清晰。首先我们要对 OpenHarmony 的 HDF(Hardware Driver Foundation)驱动框架做一定了解,最好是动手写一个 Demo 案例,具体的可以单独去官网查阅 HDF 资料。

首先,找到 hril_hdf.c 文件的代码,它承担的是驱动业务部分,源码中是不带中文注释的,为了梳理清楚流程,我给源码关键部分加上了中文注释。

/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "hril_hdf.h"

#include <stdlib.h>
#include <libudev.h>
#include <pthread.h>

#include "dfx_signal_handler.h"
#include "parameter.h"

#include "modem_adapter.h"
#include "telephony_log_c.h"

#define RIL_VENDOR_LIB_PATH "persist.sys.radio.vendorlib.path"
#define BASE_HEX 16

static struct HRilReport g_reportOps = {
    OnCallReport,
    OnDataReport,
    OnModemReport,
    OnNetworkReport,
    OnSimReport,
    OnSmsReport,
    OnTimerCallback
};

static int32_t GetVendorLibPath(char *path)
{
    int32_t code = GetParameter(RIL_VENDOR_LIB_PATH, "", path, PARAMETER_SIZE);
    if (code <= 0) {
        TELEPHONY_LOGE("Failed to get vendor library path through system properties. err:%{public}d", code);
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

static UsbDeviceInfo *GetPresetInformation(const char *vId, const char *pId)
{
    char *out = NULL;
    UsbDeviceInfo *uDevInfo = NULL;
    int32_t idVendor = (int32_t)strtol(vId, &out, BASE_HEX);
    int32_t idProduct = (int32_t)strtol(pId, &out, BASE_HEX);
    for (uint32_t i = 0; i < sizeof(g_usbModemVendorInfo) / sizeof(UsbDeviceInfo); i++) {
        if (g_usbModemVendorInfo[i].idVendor == idVendor && g_usbModemVendorInfo[i].idProduct == idProduct) {
            TELEPHONY_LOGI("list index:%{public}d", i);
            uDevInfo = &g_usbModemVendorInfo[i];
            break;
        }
    }
    return uDevInfo;
}

static UsbDeviceInfo *GetUsbDeviceInfo(void)
{
    struct udev *udev;
    struct udev_enumerate *enumerate;
    struct udev_list_entry *devices, *dev_list_entry;
    struct udev_device *dev;
    UsbDeviceInfo *uDevInfo = NULL;

    udev = udev_new();
    if (udev == NULL) {
        TELEPHONY_LOGE("Can't create udev");
        return uDevInfo;
    }
    enumerate = udev_enumerate_new(udev);
    if (enumerate == NULL) {
        TELEPHONY_LOGE("Can't create enumerate");
        return uDevInfo;
    }
    udev_enumerate_add_match_subsystem(enumerate, "tty");
    udev_enumerate_scan_devices(enumerate);
    devices = udev_enumerate_get_list_entry(enumerate);
    udev_list_entry_foreach(dev_list_entry, devices) {
        const char *path = udev_list_entry_get_name(dev_list_entry);
        if (path == NULL) {
            continue;
        }
        dev = udev_device_new_from_syspath(udev, path);
        if (dev == NULL) {
            continue;
        }
        dev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
        if (!dev) {
            TELEPHONY_LOGE("Unable to find parent usb device.");
            return uDevInfo;
        }
        const char *cIdVendor = udev_device_get_sysattr_value(dev, "idVendor");
        const char *cIdProduct = udev_device_get_sysattr_value(dev, "idProduct");
        uDevInfo = GetPresetInformation(cIdVendor, cIdProduct);
        udev_device_unref(dev);
        if (uDevInfo != NULL) {
            break;
        }
    }
    udev_enumerate_unref(enumerate);
    udev_unref(udev);
    return uDevInfo;
}

static void LoadVendor(void)
{
    const char *rilLibPath = NULL;
    char vendorLibPath[PARAMETER_SIZE] = {0};
    // Pointer to ril init function in vendor ril
    const HRilOps *(*rilInitOps)(const struct HRilReport *) = NULL;
    // functions returned by ril init function in vendor ril
    const HRilOps *ops = NULL;

    UsbDeviceInfo *uDevInfo = GetUsbDeviceInfo();
    if (GetVendorLibPath(vendorLibPath) == HDF_SUCCESS) {
        rilLibPath = vendorLibPath;
    } else if (uDevInfo != NULL) {
        rilLibPath = uDevInfo->libPath;
    } else {
        TELEPHONY_LOGI("use default vendor lib.");
        rilLibPath = g_usbModemVendorInfo[DEFAULT_MODE_INDEX].libPath;
    }
    if (rilLibPath == NULL) {
        TELEPHONY_LOGE("dynamic library path is empty");
        return;
    }

    TELEPHONY_LOGI("RilInit LoadVendor start with rilLibPath:%{public}s", rilLibPath);
    g_dlHandle = dlopen(rilLibPath, RTLD_NOW);
    if (g_dlHandle == NULL) {
        TELEPHONY_LOGE("dlopen %{public}s is fail. %{public}s", rilLibPath, dlerror());
        return;
    }
    rilInitOps = (const HRilOps *(*)(const struct HRilReport *))dlsym(g_dlHandle, "RilInitOps");
    if (rilInitOps == NULL) {
        dlclose(g_dlHandle);
        TELEPHONY_LOGE("RilInit not defined or exported");
        return;
    }
    ops = rilInitOps(&g_reportOps);
    HRilRegOps(ops);
    TELEPHONY_LOGI("HRilRegOps completed");
}

// 用来处理用户态发下来的消息
static int32_t RilAdapterDispatch(
    struct HdfDeviceIoClient *client, int32_t cmd, struct HdfSBuf *data, struct HdfSBuf *reply)
{
    int32_t ret;
    static pthread_mutex_t dispatchMutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_lock(&dispatchMutex);
    TELEPHONY_LOGI("RilAdapterDispatch cmd:%{public}d", cmd);
    ret = DispatchRequest(cmd, data);
    pthread_mutex_unlock(&dispatchMutex);
    return ret;
}

static struct IDeviceIoService g_rilAdapterService = {
    .Dispatch = RilAdapterDispatch,
    .Open = NULL,
    .Release = NULL,
};

//驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
static int32_t RilAdapterBind(struct HdfDeviceObject *device)
{
    if (device == NULL) {
        return HDF_ERR_INVALID_OBJECT;
    }
    device->service = &g_rilAdapterService;
    return HDF_SUCCESS;
}

// 驱动自身业务初始的接口
static int32_t RilAdapterInit(struct HdfDeviceObject *device)
{
    if (device == NULL) {
        return HDF_ERR_INVALID_OBJECT;
    }
    DFX_InstallSignalHandler();
    struct HdfSBuf *sbuf = HdfSbufTypedObtain(SBUF_IPC);
    if (sbuf == NULL) {
        TELEPHONY_LOGE("HdfSampleDriverBind, failed to obtain ipc sbuf");
        return HDF_ERR_INVALID_OBJECT;
    }
    if (!HdfSbufWriteString(sbuf, "string")) {
        TELEPHONY_LOGE("HdfSampleDriverBind, failed to write string to ipc sbuf");
        HdfSbufRecycle(sbuf);
        return HDF_FAILURE;
    }
    if (sbuf != NULL) {
        HdfSbufRecycle(sbuf);
    }
    TELEPHONY_LOGI("sbuf IPC obtain success!");
    LoadVendor();
    return HDF_SUCCESS;
}

// 驱动资源释放的接口
static void RilAdapterRelease(struct HdfDeviceObject *device)
{
    if (device == NULL) {
        return;
    }
    dlclose(g_dlHandle);
}

//驱动入口注册到HDF框架,这里配置的moduleName是找到Telephony模块与RIL进行通信的一个关键配置
struct HdfDriverEntry g_rilAdapterDevEntry = {
    .moduleVersion = 1,
    .moduleName = "hril_hdf",
    .Bind = RilAdapterBind,
    .Init = RilAdapterInit,
    .Release = RilAdapterRelease,
};
// 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
HDF_INIT(g_rilAdapterDevEntry);

上述代码中配置了对应该驱动的 moduleName 为"hril_hdf",因此我们需要去找到对应驱动的配置文件,以 Hi3516DV300 开发板为例,它的驱动配置在 vendor_hisilicon/ Hi3516DV300 / hdf_config / uhdf / device_info.hcs 代码中可以找到,如下:

riladapter :: host {
            hostName = "riladapter_host";
            priority = 50;
            riladapter_device :: device {
                device0 :: deviceNode {
                    policy = 2;
                    priority = 100;
                    moduleName = "libhril_hdf.z.so";
                    serviceName = "cellular_radio1";
                }
            }
        }

这里可以发现该驱动对应的服务名称为 cellular_radio1,那么 telephony_core_service 通过 HDF 与 RIL 进行通信肯定会调用到该服务名称,因此无查找 telephony_core_service 的相关代码,可以很快定位到 telephony_core_service/ services / tel_ril / src / tel_ril_manager.cpp 该代码,改代码中有一个关键类 TelRilManager,它用来负责管理 tel_ril。

看 tel_ril_manager.cpp 中的一个关键函数 ConnectRilAdapterService,它就是用来通过 HDF 框架获取RIL_ADAPTER 的服务,之前定义过 RIL_ADAPTER_SERVICE_NAME 常量为 “cellular_radio1”,它就是在 vendor_hisilicon/ XXXX / hdf_config / uhdf / device_info.hcs 中配置的 hril_hdf 驱动对应的服务名称。

bool TelRilManager::ConnectRilAdapterService()
{
    std::lock_guard<std::mutex> lock_l(mutex_);
    rilAdapterRemoteObj_ = nullptr;
    auto servMgr_ = OHOS::HDI::ServiceManager::V1_0::IServiceManager::Get();
    if (servMgr_ == nullptr) {
        TELEPHONY_LOGI("Get service manager error!");
        return false;
    }

    //通过HDF框架获取RIL_ADAPTER的服务,之前定义过RIL_ADAPTER_SERVICE_NAME常量为"cellular_radio1",它就是在 vendor_hisilicon/ XXXX / hdf_config / uhdf / device_info.hcs中配置的hril_hdf驱动对应的服务名称 
    rilAdapterRemoteObj_ = servMgr_->GetService(RIL_ADAPTER_SERVICE_NAME.c_str());
    if (rilAdapterRemoteObj_ == nullptr) {
        TELEPHONY_LOGE("bind hdf error!");
        return false;
    }
    if (death_ == nullptr) {
        TELEPHONY_LOGE("create HdfDeathRecipient object failed!");
        rilAdapterRemoteObj_ = nullptr;
        return false;
    }
    if (!rilAdapterRemoteObj_->AddDeathRecipient(death_)) {
        TELEPHONY_LOGE("AddDeathRecipient hdf failed!");
        rilAdapterRemoteObj_ = nullptr;
        return false;
    }

    int32_t ret = SetCellularRadioIndication();
    if (ret != CORE_SERVICE_SUCCESS) {
        TELEPHONY_LOGE("SetCellularRadioIndication error, ret:%{public}d", ret);
        return false;
    }
    ret = SetCellularRadioResponse();
    if (ret != CORE_SERVICE_SUCCESS) {
        TELEPHONY_LOGE("SetCellularRadioResponse error, ret:%{public}d", ret);
        return false;
    }

    return true;
}

为了帮助到大家能够更有效的学习OpenHarmony 开发的内容,下面特别准备了一些相关的参考学习资料:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

在这里插入图片描述

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Open AI】GPT-4o深夜发布:视觉、听觉跨越式升级

北京时间5月14日1点整&#xff0c;OpenAI 召开了首场春季发布会&#xff0c;CTO Mira Murati 在台上和团队用短短不到30分钟的时间&#xff0c;揭开了最新旗舰模型 GPT-4o 的神秘面纱&#xff0c;以及基于 GPT-4o 的 ChatGPT&#xff0c;均为免费使用。 本文内容来自OpenAI网站…

微服务架构:注册中心 Eureka、ZooKeeper、Consul、Nacos的选型对比详解

微服务架构&#xff08;Microservices Architecture&#xff09;是一种基于服务拆分的分布式架构模式&#xff0c;旨在将复杂的单体应用程序拆分为一组更小、更独立的服务单元。这些服务单元可以独立开发、测试、部署&#xff0c;并使用不同的技术栈和编程语言。它们通过轻量级…

外贸业务中的12个“坑”,你踩到了吗?

在竞争激烈的外贸领域&#xff0c;企业在拓展市场的同时&#xff0c;也面临着各种潜在的陷阱和风险。对于外贸公司而言&#xff0c;如何在复杂的交易过程中识破陷阱&#xff0c;防范潜在风险&#xff0c;成为确保企业长远发展的关键一环。 以下是一些外贸企业可能遇到的陷阱&a…

Nebula街机模拟器 Mac移植版(400+游戏roms)汉化版

nebula星云模拟器是电脑上最热门的街机游戏模拟器之一&#xff0c;玩家可以通过这个小巧的模拟器软件进行多款经典街机游戏启动和畅玩&#xff0c;本次移植的包含400多款游戏roms&#xff0c;经典的三国志、三国战纪、拳皇、街霸、合金弹头、1941都包含在内。 下载地址&#xf…

电感式传感器

电感传感器是基于电磁感应原理&#xff0c;将被测非电量&#xff08;如位移、压力、振动等&#xff09;转换为电感量变化的一种结构性传感器。利用自感原理的有自感式传感器&#xff08;可变磁阻式&#xff09;&#xff0c;利用互感原理的有互感式&#xff08;差动变压器式和涡…

aigc在前端中的应用-CodeGeex

前言&#xff1a;目前市场上优秀的智能编程助手有很多&#xff0c;其中以GitHub Copilot&#xff0c;Tabnine为最&#xff0c;但是目前这两款优质的智能编程助手都是需要付费的。如果不选择花费的话&#xff0c;在这里我们向小伙伴推荐免费的智能编程助手codegeex&#xff0c;性…

嵌入式——AStyle格式化工具

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 keil配置快捷键配置 AStyle&#xff08;Artistic Style&#xff09;是一个开源的代码自动格式化工具&#xff0c;可以用于自动化代…

## 23 使用BERT进行文本分类:PyTorch实战指南

文章目录 前言理解BERTPyTorch环境搭建数据准备模型建立训练模型模型评估与应用结论 前言 文本分类是自然语言处理&#xff08;NLP&#xff09;领域的一项基本任务&#xff0c;它的目的是将一个文本序列指派到一个或多个类别中。这项技术被广泛应用于垃圾邮件检测、情感分析、…

月薪20K+的策划人简历应该怎么写?

一般咱们大多数策划在写简历前&#xff0c;都是先直接找模板&#xff0c;然后按照模板的框架直接往里面填内容。 最后草草收场&#xff0c;直接拿去海投简历&#xff0c;结果发现没有拿到任何面试邀约。 策划写简历前的第一件事要梳理自己的能力模型和岗位JD。 因为只有先梳…

做DFMEA最难点,功能分析如何做?——FMEA软件

​免费试用FMEA软件-免费版-SunFMEADFMEA&#xff0c;即设计失效模式与影响分析&#xff0c;是一种在产品设计阶段就预见并预防潜在失效模式的重要工具。然而&#xff0c;在DFMEA的众多步骤中&#xff0c;功能分析无疑是其中的一大难点。它要求我们深入理解产品的各个系统、部件…

Excel 计算多个日期区间的交集中的工作日数

Excel表格有多对起止时间形成了区间组&#xff0c;如B3:C3共12组时间区间 ABCDEF12Ramadan StartsRamadan Ends323-Apr-2022-May-20Date11-Apr-24412-Apr-2111-May-21Date212-Apr-2452-Apr-221-May-22Expected6622-Mar-2320-Apr-23Caculated710-Mar-248-Apr-24828-Feb-2529-Ma…

ArcGIS arcpy代码工具——关于标识码的那些事(查找最大标识码、唯一性检查、重排序、空值赋值)

系列文章目录 ArcGIS arcpy代码工具——批量对MXD文件的页面布局设置修改 ArcGIS arcpy代码工具——数据驱动工具批量导出MXD文档并同步导出图片 ArcGIS arcpy代码工具——将要素属性表字段及要素截图插入word模板 ArcGIS arcpy代码工具——定制属性表字段输出表格 ArcGIS arc…

碳纳米管须状触嗅觉多模态融合传感器在皮革奢侈品真伪鉴定下的设计探索

一、设计方案 1.传感器选择 触觉传感器&#xff1a;选择基于碳纳米管&#xff08;CNT&#xff09;聚合物的柔性MEMS触觉微传感器&#xff0c;由于碳纳米管具有高度的灵敏度和选择性、柔韧性&#xff0c;可以作为触觉传感器&#xff0c;检测材料的微观结构和机械特性。嗅觉传感…

防火墙组网

防火墙的职责 控制和防护——在安全策略上即可体现&#xff0c;防火墙可以根据安全策略来抓取流量&#xff0c;之后做出对应的动作。吞吐量表示防火墙同一时间处理的数据量。传统防火墙(包过滤防火墙)&#xff0c;相当于一个严格的规则表&#xff0c;和ACL&#xff08;访问控制…

vue3 ElementUI 日期禁选当日前, 当日后,3天后

今日之前禁用 代码: ( 主要是 :disabledDate“disabledDateFun” ) <el-date-picker v-model"queryForm.selectedDate"type"date"range-separator"-"placeholder"选择日期":disabledDate"disabledDateFun" clearable /&…

性能测试工具—jmeter的基础使用

1.Jmeter三个重要组件 1.1线程组的介绍&#xff1a; 特点&#xff1a; 模拟用户&#xff0c;支持多用户操作多个线程组可以串行执行&#xff0c;也可以并行执行 线程组的分类&#xff1a; setup线程组&#xff1a;前置处理&#xff0c;初始化普通线程组&#xff1a;编写…

echers配置项:数据过多时,折叠数据缩放查看

当数据过多时&#xff0c;如上图所示的时间点&#xff0c;会自动折叠&#xff0c;此时鼠标缩放还不起作用&#xff0c;我们配置如下代码 let option {dataZoom: [{startValue: 05:00}, // 这个值需要跟 第一条 时间数据对应上{type: inside}], }配置后&#xff0c;就可以进行…

6个黑科技网站,用过才知真的好!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 迅捷画图 网址&#xff1a;weavesilk.com/ 这个网站虽然不怎么好玩&#xff0c;但确实是一个很好用的思维导图和流程图在线制作网站&#xff0…

哪款桌面便签app能帮助我提升工作效率

作为上班族&#xff0c;我们每天都要处理大量的工作事项&#xff0c;从策划方案到处理邮件&#xff0c;每一个环节都需高效且有条不紊。在这样的工作环境下&#xff0c;提升效率显得尤为重要。而选择一款优秀的桌面便签app&#xff0c;无疑是提高工作效率的关键。 桌面便签app…

对话YashanDB CTO陈志标:如何推动国产数据库长远发展

深圳计算科学研究院&#xff08;以下简称“深算院”&#xff09;是深圳市人民政府2018年11月批准建设的“十大基础研究机构”之一&#xff0c;由深圳市科技创新委员会主管、深圳大学举办、深圳市龙华区人民政府共建的二类事业法人单位。 崖山数据库系统YashanDB是深算院完全自主…