openh264解码h264视频帧主流程
一 解析一帧的入口
int32_t WelsDecodeSlice (PWelsDecoderContext pCtx, bool bFirstSliceInLayer, PNalUnit pNalCur) {
// 解码slice
PDqLayer pCurDqLayer = pCtx->pCurDqLayer;
PFmo pFmo = pCtx->pFmo;
int32_t iRet;
int32_t iNextMbXyIndex, iSliceIdc;
PSlice pSlice = &pCurDqLayer->sLayerInfo.sSliceInLayer;
PSliceHeaderExt pSliceHeaderExt = &pSlice->sSliceHeaderExt;
PSliceHeader pSliceHeader = &pSliceHeaderExt->sSliceHeader;
int32_t iMbX, iMbY;
const int32_t kiCountNumMb = pSliceHeader->pSps->uiTotalMbCount; //need to be correct when fmo or multi slice
uint32_t uiEosFlag = 0;
PWelsDecMbFunc pDecMbFunc;
pSlice->iTotalMbInCurSlice = 0; //initialize at the starting of slice decoding.
if (pCtx->pPps->bEntropyCodingModeFlag) { //cabac 熵编码
if (pSlice->sSliceHeaderExt.bAdaptiveMotionPredFlag ||
pSlice->sSliceHeaderExt.bAdaptiveBaseModeFlag ||
pSlice->sSliceHeaderExt.bAdaptiveResidualPredFlag) {
WelsLog (& (pCtx->sLogCtx), WELS_LOG_ERROR,
"WelsDecodeSlice()::::ILP flag exist, not supported with CABAC enabled!");
pCtx->iErrorCode |= dsBitstreamError;
return dsBitstreamError;
}
if (P_SLICE == pSliceHeader->eSliceType) //P Slice帧解析
pDecMbFunc = WelsDecodeMbCabacPSlice;
else if (B_SLICE == pSliceHeader->eSliceType) // B slice解析
pDecMbFunc = WelsDecodeMbCabacBSlice;
else //I_SLICE. B_SLICE is being supported
pDecMbFunc = WelsDecodeMbCabacISlice; // I Slice解析
} else { //
if (P_SLICE == pSliceHeader->eSliceType) {
pDecMbFunc = WelsDecodeMbCavlcPSlice;
} else if (B_SLICE == pSliceHeader->eSliceType) {
pDecMbFunc = WelsDecodeMbCavlcBSlice;
} else { //I_SLICE
pDecMbFunc = WelsDecodeMbCavlcISlice;
}
}
二 解析P slice函数
int32_t WelsDecodeMbCabacPSlice (PWelsDecoderContext pCtx, PNalUnit pNalCur, uint32_t& uiEosFlag) {
PDqLayer pCurDqLayer = pCtx->pCurDqLayer; //当前解码上下文
PSlice pSlice = &pCurDqLayer->sLayerInfo.sSliceInLayer; // slice上下文
PSliceHeader pSliceHeader = &pSlice->sSliceHeaderExt.sSliceHeader; // slice header
PPicture* ppRefPic = pCtx->sRefPic.pRefList[LIST_0]; //参考帧上下文
uint32_t uiCode;
int32_t iMbXy = pCurDqLayer->iMbXyIndex;
int32_t i;
SWelsNeighAvail uiNeighAvail;
pCurDqLayer->pCbp[iMbXy] = 0; //code block parttern信息
pCurDqLayer->pCbfDc[iMbXy] = 0;
pCurDqLayer->pChromaPredMode[iMbXy] = C_PRED_DC;
pCurDqLayer->pNoSubMbPartSizeLessThan8x8Flag[iMbXy] = true;
pCurDqLayer->pTransformSize8x8Flag[iMbXy] = false;
GetNeighborAvailMbType (&uiNeighAvail, pCurDqLayer); //填充邻居mb_type 为当前宏块解码做准备
WELS_READ_VERIFY (ParseSkipFlagCabac (pCtx, &uiNeighAvail, uiCode)); //解析是否为skip块
if (uiCode) {
int16_t pMv[2] = {0};
pCurDqLayer->pDec->pMbType[iMbXy] = MB_TYPE_SKIP;
ST32 (&pCurDqLayer->pNzc[iMbXy][0], 0);
ST32 (&pCurDqLayer->pNzc[iMbXy][4], 0);
ST32 (&pCurDqLayer->pNzc[iMbXy][8], 0);
ST32 (&pCurDqLayer->pNzc[iMbXy][12], 0);
ST32 (&pCurDqLayer->pNzc[iMbXy][16], 0);
ST32 (&pCurDqLayer->pNzc[iMbXy][20], 0);
pCurDqLayer->pInterPredictionDoneFlag[iMbXy] = 0;
memset (pCurDqLayer->pDec->pRefIndex[0][iMbXy], 0, sizeof (int8_t) * 16);
bool bIsPending = GetThreadCount (pCtx) > 1;
pCtx->bMbRefConcealed = pCtx->bRPLRError || pCtx->bMbRefConcealed || ! (ppRefPic[0] && (ppRefPic[0]->bIsComplete
|| bIsPending));
//predict mv
PredPSkipMvFromNeighbor (pCurDqLayer, pMv); //通过周围块的信息,计算得到当前块的mvp
for (i = 0; i < 16; i++) {
ST32 (pCurDqLayer->pDec->pMv[0][iMbXy][i], * (uint32_t*)pMv);
ST32 (pCurDqLayer->pMvd[0][iMbXy][i], 0);
}
//if (!pSlice->sSliceHeaderExt.bDefaultResidualPredFlag) {
// memset (pCurDqLayer->pScaledTCoeff[iMbXy], 0, 384 * sizeof (int16_t));
//}
//reset rS
pCurDqLayer->pLumaQp[iMbXy] = pSlice->iLastMbQp; //??????????????? dqaunt of previous mb 前一个qp,当前
//解码得到 delta + 这里的qp就是实际qp
for (i = 0; i < 2; i++) {
pCurDqLayer->pChromaQp[iMbXy][i] = g_kuiChromaQpTable[WELS_CLIP3 (pCurDqLayer->pLumaQp[iMbXy] +
pSliceHeader->pPps->iChromaQpIndexOffset[i], 0, 51)];
//计算得到chromaqp,和lumaqp有对应关系
}
//for neighboring CABAC usage
pSlice->iLastDeltaQp = 0;
WELS_READ_VERIFY (ParseEndOfSliceCabac (pCtx, uiEosFlag));
return ERR_NONE;
}
WELS_READ_VERIFY (WelsDecodeMbCabacPSliceBaseMode0 (pCtx, &uiNeighAvail, uiEosFlag)); //实际解析slice 中MB列表
return ERR_NONE;
三 解析Bslice过程
int32_t WelsDecodeMbCabacBSlice (PWelsDecoderContext pCtx, PNalUnit pNalCur, uint32_t& uiEosFlag) {
//解析B slice
PDqLayer pCurDqLayer = pCtx->pCurDqLayer; //当前帧上下文
PSlice pSlice = &pCurDqLayer->sLayerInfo.sSliceInLayer; //Slice指针
PSliceHeader pSliceHeader = &pSlice->sSliceHeaderExt.sSliceHeader; //当前slice头指着
PPicture* ppRefPicL0 = pCtx->sRefPic.pRefList[LIST_0]; //前向参考列表
PPicture* ppRefPicL1 = pCtx->sRefPic.pRefList[LIST_1]; //后向参考列表
uint32_t uiCode; //
int32_t iMbXy = pCurDqLayer->iMbXyIndex;
int32_t i;
SWelsNeighAvail uiNeighAvail;
pCurDqLayer->pCbp[iMbXy] = 0; //
pCurDqLayer->pCbfDc[iMbXy] = 0;
pCurDqLayer->pChromaPredMode[iMbXy] = C_PRED_DC;
pCurDqLayer->pNoSubMbPartSizeLessThan8x8Flag[iMbXy] = true;
pCurDqLayer->pTransformSize8x8Flag[iMbXy] = false;
GetNeighborAvailMbType (&uiNeighAvail, pCurDqLayer); //填充周围宏块信息
WELS_READ_VERIFY (ParseSkipFlagCabac (pCtx, &uiNeighAvail, uiCode)); //解析 skip标记
memset (pCurDqLayer->pDirect[iMbXy], 0, sizeof (int8_t) * 16);
bool bIsPending = GetThreadCount (pCtx) > 1;
if (uiCode) {
int16_t pMv[LIST_A][2] = { {0, 0}, { 0, 0 } };
int8_t ref[LIST_A] = { 0 };
pCurDqLayer->pDec->pMbType[iMbXy] = MB_TYPE_SKIP | MB_TYPE_DIRECT;
ST32 (&pCurDqLayer->pNzc[iMbXy][0], 0);
ST32 (&pCurDqLayer->pNzc[iMbXy][4], 0);
ST32 (&pCurDqLayer->pNzc[iMbXy][8], 0);
ST32 (&pCurDqLayer->pNzc[iMbXy][12], 0);
ST32 (&pCurDqLayer->pNzc[iMbXy][16], 0);
ST32 (&pCurDqLayer->pNzc[iMbXy][20], 0);
pCurDqLayer->pInterPredictionDoneFlag[iMbXy] = 0;
memset (pCurDqLayer->pDec->pRefIndex[LIST_0][iMbXy], 0, sizeof (int8_t) * 16);
memset (pCurDqLayer->pDec->pRefIndex[LIST_1][iMbXy], 0, sizeof (int8_t) * 16);
pCtx->bMbRefConcealed = pCtx->bRPLRError || pCtx->bMbRefConcealed || ! (ppRefPicL0[0] && (ppRefPicL0[0]->bIsComplete
|| bIsPending)) || ! (ppRefPicL1[0] && (ppRefPicL1[0]->bIsComplete || bIsPending));
if (pCtx->bMbRefConcealed) {
SLogContext* pLogCtx = & (pCtx->sLogCtx);
WelsLog (pLogCtx, WELS_LOG_ERROR, "Ref Picture for B-Slice is lost, B-Slice decoding cannot be continued!");
return GENERATE_ERROR_NO (ERR_LEVEL_SLICE_DATA, ERR_INFO_REFERENCE_PIC_LOST);
}
SubMbType subMbType;
if (pSliceHeader->iDirectSpatialMvPredFlag) { //Direct 块类型判断
//predict direct spatial mv
int32_t ret = PredMvBDirectSpatial (pCtx, pMv, ref, subMbType);
if (ret != ERR_NONE) {
return ret;
}
} else {
//temporal direct mode
int32_t ret = PredBDirectTemporal (pCtx, pMv, ref, subMbType);
if (ret != ERR_NONE) {
return ret;
}
}
//reset rS
pCurDqLayer->pLumaQp[iMbXy] = pSlice->iLastMbQp; //??????????????? dqaunt of previous mb
for (i = 0; i < 2; i++) {
pCurDqLayer->pChromaQp[iMbXy][i] = g_kuiChromaQpTable[WELS_CLIP3 (pCurDqLayer->pLumaQp[iMbXy] +
pSliceHeader->pPps->iChromaQpIndexOffset[i], 0, 51)];
}
//for neighboring CABAC usage
pSlice->iLastDeltaQp = 0;
WELS_READ_VERIFY (ParseEndOfSliceCabac (pCtx, uiEosFlag));
return ERR_NONE;
}
WELS_READ_VERIFY (WelsDecodeMbCabacBSliceBaseMode0 (pCtx, &uiNeighAvail, uiEosFlag)); //实际解析BSlice中的mb
return ERR_NONE;
}
四 解析ISlice过程
int32_t WelsDecodeMbCabacISliceBaseMode0 (PWelsDecoderContext pCtx, uint32_t& uiEosFlag) {
//解析I slice
PDqLayer pCurDqLayer = pCtx->pCurDqLayer; //当前帧解码上下文
PBitStringAux pBsAux = pCurDqLayer->pBitStringAux;
PSlice pSlice = &pCurDqLayer->sLayerInfo.sSliceInLayer; //slice上下文
PSliceHeader pSliceHeader = &pSlice->sSliceHeaderExt.sSliceHeader;
SWelsNeighAvail sNeighAvail;
int32_t iScanIdxStart = pSlice->sSliceHeaderExt.uiScanIdxStart;
int32_t iScanIdxEnd = pSlice->sSliceHeaderExt.uiScanIdxEnd;
int32_t iMbXy = pCurDqLayer->iMbXyIndex;
int32_t i;
uint32_t uiMbType = 0, uiCbp = 0, uiCbpLuma = 0, uiCbpChroma = 0;
ENFORCE_STACK_ALIGN_1D (uint8_t, pNonZeroCount, 48, 16);
pCurDqLayer->pNoSubMbPartSizeLessThan8x8Flag[iMbXy] = true;
pCurDqLayer->pTransformSize8x8Flag[iMbXy] = false;
pCurDqLayer->pInterPredictionDoneFlag[iMbXy] = 0;
pCurDqLayer->pResidualPredFlag[iMbXy] = pSlice->sSliceHeaderExt.bDefaultResidualPredFlag;
GetNeighborAvailMbType (&sNeighAvail, pCurDqLayer);
WELS_READ_VERIFY (ParseMBTypeISliceCabac (pCtx, &sNeighAvail, uiMbType)); //解析mb类型
if (uiMbType > 25) { //非法的mb类型
return GENERATE_ERROR_NO (ERR_LEVEL_MB_DATA, ERR_INFO_INVALID_MB_TYPE);
} else if (!pCtx->pSps->uiChromaFormatIdc && ((uiMbType >= 5 && uiMbType <= 12) || (uiMbType >= 17
&& uiMbType <= 24))) { //如果没有色度信息,这里mb类型不合法的类型判断
return GENERATE_ERROR_NO (ERR_LEVEL_MB_DATA, ERR_INFO_INVALID_MB_TYPE);
} else if (25 == uiMbType) { //I_PCM
WelsLog (& (pCtx->sLogCtx), WELS_LOG_DEBUG, "I_PCM mode exists in I slice!");
WELS_READ_VERIFY (ParseIPCMInfoCabac (pCtx)); //解析PCM 信息,cabac
pSlice->iLastDeltaQp = 0;
WELS_READ_VERIFY (ParseEndOfSliceCabac (pCtx, uiEosFlag));
if (uiEosFlag) {
RestoreCabacDecEngineToBS (pCtx->pCabacDecEngine, pCtx->pCurDqLayer->pBitStringAux);
}
return ERR_NONE;
} else if (0 == uiMbType) { //I4x4 16个4x4的帧内块
ENFORCE_STACK_ALIGN_1D (int8_t, pIntraPredMode, 48, 16);
pCurDqLayer->pDec->pMbType[iMbXy] = MB_TYPE_INTRA4x4;
相关文章:
openh264解码h264视频帧主流程
一 解析一帧的入口int32_t WelsDecodeSlice (PWelsDecoderContext pCtx, bool bFirstSliceInLayer, PNalUnit pNalCur) {// 解码slicePDqLayer pCurDqLayer pCtx->pCurDqLayer;PFmo pFmo pCtx->pFmo;int32_t iRet;int32_t iNextMbXyIndex, iSliceIdc;PSlice pSlice &a…...
【个人笔记】C语言位域
一句话解释位域:指定结构体内变量的的位宽,从而节省空间 例子: #include <stdio.h> struct _test {int bit1:3; // 第一个字节0 ~ 2位 int :0; // 空域:表示第一个字节 3~7都为0int bit2:1; // 第二个字节第0位int :3;…...
ROS笔记(1)——ROS的核心概念
目录 节点与管理器 话题通信 服务通信 参数——全局共享字典 节点与管理器 节点——执行单元 (1)执行具体任务的进程、独立运行的可执行文件 (2)不同节点可使用不同的编程语言,可分布式运行在不同的主机 ÿ…...

动态SQL使用【JavaEE】
动态SQL使用 1. if 标签 判断一个参数是否有值,如果没值,那么就会隐藏 if 中的 sql 语法: <if test"username!null">username#{username} </if>表达式:username 的参数是否为空 如果结果为 true,…...
leetcode刷题
1、stack栈相关 top():返回一个栈顶元素的引用,类型为 T&。如果栈为空,返回值未定义。 push(const T& obj):可以将对象副本压入栈顶。这是通过调用底层容器的 push_back() 函数完成的。 push(T&& obj)࿱…...

移动设备安全管理基础指南
什么是移动安全管理 (MSM) 移动安全管理是指为保护企业中的移动设备和企业数据而采取的行动。这些操作可以进一步被归类为反应性的或主动的,基于该操作是在数据和设备被破坏之前还是之后执行的。除了管理移动设备外,大多数MDM解决…...

【Java|多线程与高并发】 使用Thread 类创建线程的5种方法如何查看程序中的线程
文章目录前言线程创建1.继承Thread类重写run()方法如何查看程序中的线程?2.实现Runnable接口3.使用匿名内部类,继承Thread4.使用匿名内部类,实现Runnable5.⭐使用Lambda表达式,创建线程(重要)Thread 的常见构造方法总结前言 在这里主要补充说明一些问题,方便更好地理解下面的…...

零基础学MySQL(五)-- 详细讲解数据库中的常用函数
目录🎇一、聚合函数1️⃣count 函数(1)基本语法(2)基本练习(3)注意细节2️⃣sum 函数(1)基本语法(2)基本练习(3)注意细节3…...

第4章 流程控制-if-else,Switch,For循环(循环守卫,循环步长,倒叙打印),While循环,多重循环...
第 4 章 流程控制-if-else,Switch,For循环(循环守卫,循环步长,倒叙打印),While循环,多重循环 4.1 分支控制 if-else 让程序有选择的的执行,分支控制有三种:单分支、双分支、多分支 4.1.1 单分支 1)基本语法…...

2.4G-WiFi连接路由器过程
一、概述 WiFi的数据通信基于802.11协议进行,无线AP在工作时会定时向空中发送beacon数据包,基站(STA)从beacon中解析出AP的名称、加密方式等信息,从而发起连接。 二、WiFi连接路由器的详细过程 WiFi连接过程主要可以…...

3. SpringMVC Rest 风格
1. REST 简介 REST(Representational State Transfer),表现形式状态转换,它是一种软件架构风格。 当要表示一个网络资源的时候,可以使用两种方式: 传统风格资源描述形式 http://localhost/user/getById?…...
Python3简介
Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性,相比其他语言经常使用英文关键字,其他语言的一些标点符号,它具有比其他语言更有特色语法结构。 Python 是面向对象语言: 这意味着P…...

如何学习PMP?
★基础要打牢 方法:“基础不牢,地动山摇”,如果基础不牢那么就很难拿高分,因为连最基础的题目分都不一定能拿到。 可以在针对基础知识,把PMBOK看一两遍,再次加深印象,再把平时做章节练习、每日5…...

【DSP视频教程】第11期:插补算法,曲线拟合丝滑顺畅,统计函数和基础函数加速实现,汇集SIMD,饱和和MAC乘累加应用实战(2023-02-12)
视频教程汇总帖:https://www.armbbs.cn/forum.php?modviewthread&tid110519 DSP视频教程有段时间没有更新了。 当前DSP库从CMSIS软件包里面独立出来,并且更新非常频繁,所以本期视频教程优先给大家简单介绍下新版DSP, 然后为…...

分类模型评估:混淆矩阵、准确率、召回率、ROC
1. 混淆矩阵 在二分类问题中,混淆矩阵被用来度量模型的准确率。因为在二分类问题中单一样本的预测结果只有Yes or No,即:真或者假两种结果,所以全体样本的经二分类模型处理后,处理结果不外乎四种情况,每种…...

算法 ——世界 一
个人简介:云计算网络运维专业人员,了解运维知识,掌握TCP/IP协议,每天分享网络运维知识与技能。个人爱好: 编程,打篮球,计算机知识个人名言:海不辞水,故能成其大;山不辞石…...

2023年3月AMA-CDGA/CDGP数据治理认证考试这些城市可以报名
目前2023年3月5日CDGA&CDGP开放报名的城市有:北京、上海、广州、深圳、杭州、重庆,西安,成都,长沙,济南,更多考场正在增加中… DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业…...

Java变量和数据类型,超详细整理,适合新手入门
目录 一、什么是变量? 二、变量 变量值互换 三、基本数据类型 1、八种基本数据类型 2、布尔值 3、字符串 四、从控制台输入 一、什么是变量? 变量是一种存储值的容器,它可以在程序的不同部分之间共享;变量可以存储数字、字…...

Echarts 设置折线图拐点的颜色,边框等样式,hover时改变颜色
第014个点击查看专栏目录上一篇文章我们讲到了如何设置拐点大小,图形类型,旋转角度,缩放同比,位置偏移等,这篇文章介绍如何设置拐点的颜色、边框大小颜色等样式。hover轴线时候,拐点的填充颜色改变文章目录示例效果示例…...
做 SQL 性能优化真是让人干瞪眼
很多大数据计算都是用SQL实现的,跑得慢时就要去优化SQL,但常常碰到让人干瞪眼的情况。 比如,存储过程中有三条大概形如这样的语句执行得很慢: select a,b,sum(x) from T group by a,b where …; select c,d,max(y) from T grou…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...
AWS vs 阿里云:功能、服务与性能对比指南
在云计算领域,Amazon Web Services (AWS) 和阿里云 (Alibaba Cloud) 是全球领先的提供商,各自在功能范围、服务生态系统、性能表现和适用场景上具有独特优势。基于提供的引用[1]-[5],我将从功能、服务和性能三个方面进行结构化对比分析&#…...