注:写了一系列的有关ffdshow对解码器的封装的代码,列表如下:
ffdshow 源代码分析 6: 对解码器的dll的封装(libavcodec)
ffdshow 源代码分析 7: libavcodec视频解码器类(TvideoCodecLibavcodec)
ffdshow 源代码分析 8: 视频解码器类(TvideoCodecDec)
ffdshow 源代码分析 9: 编解码器有关类的总结
==========
前面两篇文章介绍了ffdshow中libavcodec的封装类Tlibavcodec,以及libavcodec的解码器类TvideoCodecLibavcodec:
ffdshow 源代码分析 6: 对解码器的dll的封装(libavcodec)
ffdshow 源代码分析 7: 解码器类(TvideoCodecLibavcodec)
其中libavcodec的解码器类TvideoCodecLibavcodec通过调用Tlibavcodec中的方法实现了libavcodec的dll中方法的调用;而它继承了TvideoCodecDec,本文正是要分析它继承的这个类。
TvideoCodecDec是所有视频解码器共有的父类。可以看一下它的继承关系:
可见,除了TvideoCodecLibavcodec继承了TvideoCodecDec之外,还有好几个类继承了TvideoCodecDec,比如说:TvideoCodecLibmpeg2,TvideoCodecXviD4等等…。突然来了兴趣,我们可以看一下其他的解码器类的定义是什么样的。
TvideoCodecLibmpeg2定义如下:
/* *雷霄骅 *leixiaohua1020@126.com *中国传媒大学/数字电视技术 */ #ifndef _TVIDEOCODECLIBMPEG2_H_ #define _TVIDEOCODECLIBMPEG2_H_ #include "TvideoCodec.h" #include "libmpeg2/include/mpeg2.h" class Tdll; struct Textradata; class TccDecoder; //libmpeg2解码器 class TvideoCodecLibmpeg2 : public TvideoCodecDec { private: Tdll *dll; uint32_t (*mpeg2_set_accel)(uint32_t accel); mpeg2dec_t* (*mpeg2_init)(void); const mpeg2_info_t* (*mpeg2_info)(mpeg2dec_t *mpeg2dec); mpeg2_state_t (*mpeg2_parse)(mpeg2dec_t *mpeg2dec); void (*mpeg2_buffer)(mpeg2dec_t *mpeg2dec, const uint8_t *start, const uint8_t *end); void (*mpeg2_close)(mpeg2dec_t *mpeg2dec); void (*mpeg2_reset)(mpeg2dec_t *mpeg2dec, int full_reset); void (*mpeg2_set_rtStart)(mpeg2dec_t *mpeg2dec, int64_t rtStart); int (*mpeg2_guess_aspect)(const mpeg2_sequence_t * sequence, unsigned int * pixel_width, unsigned int * pixel_height); mpeg2dec_t *mpeg2dec; const mpeg2_info_t *info; bool wait4Iframe; int sequenceFlag; REFERENCE_TIME avgTimePerFrame; TffPict oldpict; Textradata *extradata; TccDecoder *ccDecoder; Tbuffer *buffer; uint32_t oldflags; bool m_fFilm; int SetDeinterlaceMethod(void); void init(void); HRESULT decompressI(const unsigned char *src, size_t srcLen, IMediaSample *pIn); protected: virtual bool beginDecompress(TffPictBase &pict, FOURCC infcc, const CMediaType &mt, int sourceFlags); public: TvideoCodecLibmpeg2(IffdshowBase *Ideci, IdecVideoSink *Isink); virtual ~TvideoCodecLibmpeg2(); static const char_t *dllname; virtual int getType(void) const { return IDFF_MOVIE_LIBMPEG2; } virtual int caps(void) const { return CAPS::VIS_QUANTS; } virtual void end(void); virtual HRESULT decompress(const unsigned char *src, size_t srcLen, IMediaSample *pIn); virtual bool onSeek(REFERENCE_TIME segmentStart); virtual HRESULT BeginFlush(); }; #endif
TvideoCodecXviD4定义如下:
/* *雷霄骅 *leixiaohua1020@126.com *中国传媒大学/数字电视技术 */ #ifndef _TVIDEOCODECXVID4_H_ #define _TVIDEOCODECXVID4_H_ #include "TvideoCodec.h" class Tdll; struct Textradata; //xvid解码器 class TvideoCodecXviD4 : public TvideoCodecDec { private: void create(void); Tdll *dll; public: TvideoCodecXviD4(IffdshowBase *Ideci, IdecVideoSink *IsinkD); virtual ~TvideoCodecXviD4(); int (*xvid_global)(void *handle, int opt, void *param1, void *param2); int (*xvid_decore)(void *handle, int opt, void *param1, void *param2); int (*xvid_plugin_single)(void *handle, int opt, void *param1, void *param2); int (*xvid_plugin_lumimasking)(void *handle, int opt, void *param1, void *param2); static const char_t *dllname; private: void *enchandle, *dechandle; int psnr; TffPict pict; Tbuffer pictbuf; static int me_hq(int rd3), me_(int me3); Textradata *extradata; REFERENCE_TIME rtStart, rtStop; protected: virtual bool beginDecompress(TffPictBase &pict, FOURCC infcc, const CMediaType &mt, int sourceFlags); virtual HRESULT flushDec(void); public: virtual int getType(void) const { return IDFF_MOVIE_XVID4; } virtual int caps(void) const { return CAPS::VIS_QUANTS; } virtual HRESULT decompress(const unsigned char *src, size_t srcLen, IMediaSample *pIn); }; #endif
从以上这2种解码器类的定义,我们可以看出一些规律,比如说:
1. 都有Tdll *dll这个变量,用于加载视频解码器的dll
2. 都有beginDecompress()函数,用于初始化解码器
3. 都有decompress()函数,用于解码
好了,闲话不说,回归正题,来看一下这些解码器共有的父类:TvideoCodecDec
//具体 视频 解码器的父类,存一些公共信息 class TvideoCodecDec : virtual public TvideoCodec, virtual public TcodecDec { protected: bool isdvdproc; comptrQ<IffdshowDecVideo> deciV; IdecVideoSink *sinkD; TvideoCodecDec(IffdshowBase *Ideci, IdecVideoSink *Isink); Rational guessMPEG2sar(const Trect &r, const Rational &sar2, const Rational &containerSar); class TtelecineManager { private: TvideoCodecDec* parent; int segment_count; int pos_in_group; struct { int fieldtype; int repeat_pict; REFERENCE_TIME rtStart; } group[2]; // store information about 2 recent frames. REFERENCE_TIME group_rtStart; bool film; int cfg_softTelecine; public: TtelecineManager(TvideoCodecDec* Iparent); void get_timestamps(TffPict &pict); void get_fieldtype(TffPict &pict); void new_frame(int top_field_first, int repeat_pict, const REFERENCE_TIME &rtStart, const REFERENCE_TIME &rtStop); void onSeek(void); } telecineManager; public: static TvideoCodecDec* initDec(IffdshowBase *deci, IdecVideoSink *Isink, AVCodecID codecId, FOURCC fcc, const CMediaType &mt); virtual ~TvideoCodecDec(); virtual int caps(void) const { return CAPS::NONE; } virtual bool testMediaType(FOURCC fcc, const CMediaType &mt) { return true; } virtual void forceOutputColorspace(const BITMAPINFOHEADER *hdr, int *ilace, TcspInfos &forcedCsps) { *ilace = 0; //cspInfos of forced output colorspace, empty when entering function } enum {SOURCE_REORDER = 1}; virtual bool beginDecompress(TffPictBase &pict, FOURCC infcc, const CMediaType &mt, int sourceFlags) = 0; virtual HRESULT decompress(const unsigned char *src, size_t srcLen, IMediaSample *pIn) = 0; virtual bool onDiscontinuity(void) { return false; } virtual HRESULT onEndOfStream(void) { return S_OK; } unsigned int quantsDx, quantsStride, quantsDy, quantBytes, quantType; //QP表 void *quants; uint16_t *intra_matrix, *inter_matrix; //计算平均QP float calcMeanQuant(void); //画运动矢量 virtual bool drawMV(unsigned char *dst, unsigned int dx, stride_t stride, unsigned int dy) const { return false; } virtual const char* get_current_idct(void) { return NULL; } virtual int useDXVA(void) { return 0; }; virtual void setOutputPin(IPin * /*pPin*/) {} };
TvideoCodecDec这个类中,还定义了一个类TtelecineManager。这种在类里面再定义一个类的方式还是不太多见的。TtelecineManager这个类的作用还没有研究,先不管它。
可以看出,TvideoCodecDec类的定义并不复杂,最主要的变量有如下几个,这几个变量都是子类中会用到的:
comptrQ<IffdshowDecVideo>deciV:重要性不言而喻,回头介绍
IdecVideoSink *sinkD:重要性不言而喻,回头介绍
void *quants:QP表(为什么要存在这里还没搞清)
TvideoCodecDec类定义了几个函数:
initDec():初始化解码器(重要)
calcMeanQuant():计算平均QP(为什么要在这里计算还没搞清)
TvideoCodecDec类还定义了一些纯虚函数,作为接口,这些函数的实现都在TvideoCodecDec的子类中完成【这几个函数是最重要的】:
beginDecompress();
decompress();
TvideoCodecDec类中最重要的函数只有一个,就是initDec(),作用主要是初始化解码器。其他的很多函数大多只是定义了一个名称,并没有实现,因为都是打算在具体各种解码器类中再进行实现的。
看一下initDec()的代码:
TvideoCodecDec* TvideoCodecDec::initDec(IffdshowBase *deci, IdecVideoSink *sink, AVCodecID codecId, FOURCC fcc, const CMediaType &mt) { // DXVA mode is a preset setting switch (codecId) { case AV_CODEC_ID_H264: if (deci->getParam2(IDFF_filterMode) & IDFF_FILTERMODE_VIDEODXVA) { if (deci->getParam2(IDFF_dec_DXVA_H264)) { codecId = CODEC_ID_H264_DXVA; } else { return NULL; } } break; case AV_CODEC_ID_VC1: case CODEC_ID_WMV9_LIB: if (deci->getParam2(IDFF_filterMode) & IDFF_FILTERMODE_VIDEODXVA) { if (deci->getParam2(IDFF_dec_DXVA_VC1)) { codecId = CODEC_ID_VC1_DXVA; } else { return NULL; } } break; default: break; } TvideoCodecDec *movie = NULL; if (is_quicksync_codec(codecId)) { movie = new TvideoCodecQuickSync(deci, sink, codecId); } else if (lavc_codec(codecId)) { movie = new TvideoCodecLibavcodec(deci, sink); } else if (raw_codec(codecId)) { movie = new TvideoCodecUncompressed(deci, sink); } else if (wmv9_codec(codecId)) { movie = new TvideoCodecWmv9(deci, sink); } else if (codecId == CODEC_ID_XVID4) { movie = new TvideoCodecXviD4(deci, sink); } else if (codecId == CODEC_ID_LIBMPEG2) { movie = new TvideoCodecLibmpeg2(deci, sink); } else if (codecId == CODEC_ID_AVISYNTH) { movie = new TvideoCodecAvisynth(deci, sink); } else if (codecId == CODEC_ID_H264_DXVA || codecId == CODEC_ID_VC1_DXVA) { movie = new TvideoCodecLibavcodecDxva(deci, sink, codecId); } else { return NULL; } if (!movie) { return NULL; } if (movie->ok && movie->testMediaType(fcc, mt)) { movie->codecId = codecId; return movie; } else if (is_quicksync_codec(codecId)) { // QuickSync decoder init failed, revert to internal decoder. switch (codecId) { case CODEC_ID_H264_QUICK_SYNC: codecId = AV_CODEC_ID_H264; break; case CODEC_ID_MPEG2_QUICK_SYNC: codecId = CODEC_ID_LIBMPEG2; break; case CODEC_ID_VC1_QUICK_SYNC: codecId = CODEC_ID_WMV9_LIB; break; default: ASSERT(FALSE); // this shouldn't happen! } delete movie; // Call this function again with the new codecId. return initDec(deci, sink, codecId, fcc, mt); } else { delete movie; return NULL; } }
这个函数的功能还是比较好理解的,根据CodecID的不同,创建不同的解码器(从TvideoCodecLibavcodec,TvideoCodecXviD4,TvideoCodecLibmpeg2这些里面选择)。
虽然不知道用途是什么,但是我们可以顺便看一下计算平均QP的函数,就是把quants1指向的QP表里面的数据求了一个平均值:
//计算平均QP float TvideoCodecDec::calcMeanQuant(void) { if (!quants || !quantsDx || !quantsDy) { return 0; } unsigned int sum = 0, num = quantsDx * quantsDy; unsigned char *quants1 = (unsigned char*)quants; for (unsigned int y = 0; y < quantsDy; y++) for (unsigned int x = 0; x < quantsDx; x++) { sum += quants1[(y * quantsStride + x) * quantBytes]; } return float(sum) / num; }
相关推荐
ffdshow 源代码分析 8: 视频解码器类(TvideoCodecDec) 344 ffdshow 源代码分析 9: 编解码器有关类的总结 352 9.2 LAV filters 357 LAV Filter 源代码分析 1: 总体结构 357 LAV Filter 源代码分析 2: LAV ...
ffdshow 源代码分析 8: 视频解码器类(TvideoCodecDec) 328 ffdshow 源代码分析 9: 编解码器有关类的总结 335 9.2 LAV filters 340 LAV Filter 源代码分析 1: 总体结构 340 LAV Filter 源代码分析 2: LAV ...
Yifengsyj_V2.4_XiTongZhiJia
该资源为joblib-0.14.1-py2.py3-none-any.whl,欢迎下载使用哦!
机械臂机械臂机械臂机械臂机械臂机械臂测试
3dmax插件
FIndSecBugs、Findbugs、infer、PMD 4个免费的SAST工具中的Java Checker
内容概要:本文详细介绍了基于STM32F103C6T6的自平衡小车的设计与实现。核心组件包括MPU6050陀螺仪用于姿态检测,通过I2C通信进行初始化配置;采用经典的PID算法实现平衡控制,重点讨论了PID参数的整定方法及其对车辆稳定性的影响;蓝牙模块HLK-B40实现了手机APP远程控制功能,提供了详细的指令解析和异常处理策略;电机驱动选用DRV8833,强调了PWM频率设置和死区时间配置的重要性。此外,文中还涉及了超声波避障、红外寻迹等功能模块的扩展思路以及一些实用技巧,如电池选择、电源管理和LED灯光效应用。 适合人群:具有一定嵌入式系统开发经验的技术爱好者、高校学生或相关领域的工程师。 使用场景及目标:适用于希望深入了解自平衡小车工作原理和技术细节的人群。主要目标是帮助读者掌握从硬件搭建到软件编程的全流程,能够独立完成类似项目的开发。 其他说明:文中不仅提供了完整的代码片段,还分享了许多实践经验,包括常见错误及解决方案,有助于初学者少走弯路。
适用于刚开始开发大彩串口屏的开发者,功能包括输入密码进入主页面和修改密码
内容概要:本文详细介绍了利用Matlab及其Yalmip工具箱,结合Gurobi求解器,实现多用户(如工业园区内的多个工厂)储能电站的日前经济调度优化。主要内容涵盖模型建立、变量定义、目标函数设定、约束条件配置以及求解过程。文中通过具体的代码实例展示了如何根据分时电价和各用户的用电需求,制定最优的储能充放电计划,从而达到降低总体电费的目的。此外,还讨论了一些常见的实现细节和技术难点,如充放电效率的正确处理、初始荷电状态(SOC)的设定等。 适合人群:具有一定编程基础并对电力系统优化感兴趣的工程师或研究人员。 使用场景及目标:适用于希望减少电费支出并提高能源利用效率的企业或机构。通过学习本文提供的方法,能够掌握如何构建和求解类似的优化问题,进而应用于实际工程项目中。 其他说明:文中提到的技术手段不仅限于储能调度,还可以扩展到其他类型的资源分配问题。对于想要深入了解优化理论及其工程应用的人来说,这是一个很好的入门案例。
内容概要:本文详细介绍了如何利用西门子S7-1200 PLC及其内置的工艺对象特性,实现对五个第三方伺服驱动器的精确控制。主要内容涵盖硬件配置、软件编程、参数设置以及常见问题解决方案。文中提供了具体的代码示例和技术细节,帮助读者理解和实施这一复杂的控制系统。此外,还分享了一些实际应用中的经验和教训,确保系统的稳定性和可靠性。 适合人群:从事工业自动化领域的工程师和技术人员,特别是那些熟悉西门子PLC编程并且希望深入了解多轴伺服控制的人群。 使用场景及目标:本指南旨在指导用户完成从硬件搭建到软件编程的全过程,最终达到高效稳定的五轴伺服控制效果。具体应用场景包括但不限于包装生产线、贴标机以及其他需要精密运动控制的机械设备。 其他说明:作者强调了多个需要注意的关键点,如脉冲信号线的选择、伺服报警信号的处理、电子齿轮比的计算等。同时提醒读者关注某些特定型号PLC的功能限制,并给出了优化建议。
内容概要:本文详细介绍了欧姆龙CP1E PLC与三菱FR-E700变频器通过Modbus RTU进行通信的方法。首先,文中列举了所需的硬件设备及其连接方法,强调了正确的接线方式和必要的硬件配置。接着,深入讲解了PLC程序的具体编写步骤,包括通讯参数初始化、正反转控制、频率设定等功能的实现。此外,还涉及了触摸屏的关键配置以及一些常见的调试技巧和注意事项。最后,作者分享了一些实际应用中的经验教训,如通讯超时重试机制、数据格式转换等。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是那些需要进行PLC与变频器通信项目的人员。 使用场景及目标:适用于需要实现PLC与变频器之间的稳定通信的工程项目,帮助技术人员快速掌握相关技术和解决实际问题。 其他说明:文中提供了大量实用的技术细节和实践经验,对于初学者来说是非常宝贵的学习资料。同时,也提到了一些容易忽视的小细节,有助于提高系统的可靠性和稳定性。
内容概要:本文详细介绍了利用非对称纳什谈判模型进行多微网电能共享优化的MATLAB代码实现。首先,通过合作博弈将多个微网的总效益最大化,然后通过议价分配子问题确保各微网按贡献公平分配收益。文中展示了如何使用ADMM算法进行分布式求解,同时考虑了电转气设备和碳捕集系统的低碳调度。实验结果显示,联合调度相比独立运行降低了14.7%的成本和22.3%的碳排放。此外,代码设计中特别强调了隐私保护,使得各微网可以在不泄露敏感数据的情况下进行协作。 适合人群:从事微电网调度优化研究的技术人员、研究生以及相关领域的研究人员。 使用场景及目标:适用于希望深入了解微电网电能共享优化策略的研究人员和技术开发者,旨在提供一种高效、公平且环保的多微网协作方法。 其他说明:代码中包含了详细的注释和数学推导,便于理解和修改。建议在配置CPLEX和MOSEK求解器环境下运行代码,以获得最佳性能。
智能穿搭_ComfyUI_模型换装_SegVITON_实时服_1744169167.zip
内容概要:本文详细介绍了如何利用MATLAB/Simulink进行电动助力转向系统(EPS)的建模及其控制策略的实现。首先,文章阐述了EPS系统的基本组成,包括方向盘模块、扭矩传感器、助力电机以及齿轮齿条机构,并针对各个部分进行了具体的数学建模。接着,重点讨论了PID控制算法的应用,包括参数整定的方法和注意事项,确保系统的稳定性和平顺性。此外,文中还涉及到了一些常见的仿真问题及其解决方案,如代数环问题、信号噪声处理等。最后,通过对仿真结果的分析,展示了所建立模型的有效性和优越性能。 适合人群:具有一定MATLAB/Simulink基础并希望深入了解汽车电子控制系统特别是EPS系统的工程师和技术爱好者。 使用场景及目标:适用于从事汽车工程领域的研究人员和技术人员,旨在帮助他们掌握EPS系统的建模方法和控制策略,提高仿真的准确性,从而更好地应用于实际产品开发中。 其他说明:文章不仅提供了详细的理论推导和代码示例,还分享了许多实践经验,有助于读者更快地理解和应用相关知识。
内容概要:本文详细介绍了特种电机(如轴向磁通电机和军用级永磁同步电机)在Maxwell和Simplorer联合仿真中的应用和技术细节。首先,文章讲解了Maxwell中三维模型的参数化设置,特别是极弧系数的自动化调整和绕组编号规则。其次,探讨了Simplorer中电路模型的构建,尤其是IGBT死区时间和PWM模块的配置。接着,强调了两者之间的耦合接口配置,包括数据传输步长和插值方式的选择。此外,文章还讨论了仿真波形的分析方法,如效率MAP图和启动瞬间电流的解读。最后,分享了一些实践经验,如将控制算法生成C代码烧入DSP进行交叉验证,以及仿真步长的分段设置。 适合人群:从事电机设计和仿真的工程师,特别是对特种电机感兴趣的科研人员和技术开发者。 使用场景及目标:适用于需要进行电磁场和控制系统联合仿真的场合,旨在提高仿真精度和效率,缩短产品研发周期。通过掌握文中提到的技术细节,可以更好地理解和解决仿真过程中常见的问题。 其他说明:文章提供了大量具体的代码示例和参数设置建议,帮助读者在实践中避免常见错误。同时,强调了仿真过程中需要注意的细节,如能量守恒检查和数据交互同步等问题。
内容概要:本文详细对比了两种不同风格的托盘输送机PLC程序,分别来自北起院的传统状态字版本(V1)和外企的面向对象版本(V2),均基于相同的硬件配置(S7-1500 PLC + KTP700 HMI)。V1版本使用大量状态字进行逻辑控制,代码复杂度较高,维护困难;而V2版本采用面向对象设计,利用功能块(FB)封装硬件抽象,极大提升了代码的可读性和维护性。文中还探讨了两者在与上位WCS系统的通信方式、报警处理、HMI界面生成等方面的异同,并分享了一些实际调试中的经验教训。 适合人群:从事工业自动化控制、PLC编程的技术人员,尤其是对托盘输送机系统感兴趣的工程师。 使用场景及目标:①理解传统状态字编程与现代面向对象编程在PLC程序中的优缺点;②掌握如何优化PLC程序结构,提高代码可维护性和开发效率;③学习如何正确配置PLC与上位系统的通信。 其他说明:文章不仅展示了两种编程方法的具体实现细节,还提供了实用的调试技巧和注意事项,有助于读者在实际工作中做出更好的技术决策。
内容概要:本文档为第八届蓝桥杯嵌入式设计与开发项目省赛的基础知识试题部分,主要考察参赛者对嵌入式系统的基本概念、逻辑运算、微控制器特性、通信接口及时钟源的选择等知识点的理解。试题涵盖逻辑表达式的化简、门电路的功能识别、STM32F103RBT6微控制器的内核及数据类型支持情况、RS232接口用于双机通信所需的最少信号线数量、STM32程序下载调试的方式选择、可菊链连接的接口类型、USB外设开发的时钟源选择、DMA的工作机制以及简单电路的电压计算等多个方面,旨在全面检验选手的专业知识掌握程度。 适合人群:具有一定的电子技术与单片机开发基础,准备参加嵌入式设计与开发竞赛的学生或爱好者。 使用场景及目标:①帮助参赛者熟悉并巩固嵌入式系统相关理论知识;②作为赛前复习资料,提高解题速度和准确性;③通过练习加深对各种硬件特性和应用场景的理解。 其他说明:文档提供了详细的答案解析,有助于学习者更好地理解题目背后的原理,建议结合实际项目经验进行学习,同时注意不同版本器件之间的差异。
网络安全_CKCsec安全研究院_Web安全_区块链安全_C_1744170559.zip
内容概要:本文深入探讨了Carsim与Simulink联合仿真在自动驾驶领域的应用,特别是针对车道保持(LKA)和其他控制场景的多种轨迹跟随算法。文中介绍了基于PID、单点预瞄、多点预瞄、模糊PID、滑模变结构控制、预瞄+滑模变结构+模糊控制+预瞄距离自适应、MPC等多种控制策略的具体实现方式及其优缺点。此外,还讨论了ACC(自适应巡航控制)、AEB(自动紧急制动)和差动驱动等其他联合仿真模型的应用。每个控制策略都有详细的代码示例和技术细节,帮助读者理解其实现机制。 适合人群:从事自动驾驶技术研发的工程师、研究人员,以及对车辆动力学仿真和控制算法感兴趣的高校师生。 使用场景及目标:①研究和验证自动驾驶中的横向控制算法;②评估不同控制策略在各种驾驶场景下的表现;③优化控制参数以提高轨迹跟随精度和稳定性;④通过仿真减少实车测试的风险和成本。 阅读建议:由于涉及大量控制理论和具体实现代码,建议读者具备一定的控制工程和MATLAB/Simulink基础。同时,对于复杂的控制策略,可以通过逐步实验和调试加深理解。