当前位置: 首页 > news >正文

音视频入门基础:MPEG2-PS专题(5)——FFmpeg源码中,解析PS流中的PES流的实现

一、引言

从《音视频入门基础:MPEG2-PS专题(3)——MPEG2-PS格式简介》中可以知道,PS流由一个个pack(包装)组成。一个pack = 一个pack_header + 一个或多个PES_packet。pack_header中还可能存在system header。

但是pack_header和system header中并没有什么重要信息,所以FFmpeg源码在解析PS流时会跳过pack_header和system header,直接解析PES packet。

FFmpeg源码中通过mpegps_read_pes_header函数解析PS流中的PES packet。

二、mpegps_read_pes_header函数的定义

mpegps_read_pes_header函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/mpeg.c中:

/* read the next PES header. Return its position in ppos* (if not NULL), and its start code, pts and dts.*/
static int mpegps_read_pes_header(AVFormatContext *s,int64_t *ppos, int *pstart_code,int64_t *ppts, int64_t *pdts)
{MpegDemuxContext *m = s->priv_data;int len, size, startcode, c, flags, header_len;int pes_ext, ext2_len, id_ext, skip;int64_t pts, dts;int64_t last_sync = avio_tell(s->pb);error_redo:avio_seek(s->pb, last_sync, SEEK_SET);
redo:/* next start code (should be immediately after) */m->header_state = 0xff;size      = MAX_SYNC_SIZE;startcode = find_next_start_code(s->pb, &size, &m->header_state);last_sync = avio_tell(s->pb);if (startcode < 0) {if (avio_feof(s->pb))return AVERROR_EOF;// FIXME we should remember header_statereturn FFERROR_REDO;}if (startcode == PACK_START_CODE)goto redo;if (startcode == SYSTEM_HEADER_START_CODE)goto redo;if (startcode == PADDING_STREAM) {avio_skip(s->pb, avio_rb16(s->pb));goto redo;}if (startcode == PRIVATE_STREAM_2) {if (!m->sofdec) {/* Need to detect whether this from a DVD or a 'Sofdec' stream */int len = avio_rb16(s->pb);int bytesread = 0;uint8_t *ps2buf = av_malloc(len);if (ps2buf) {bytesread = avio_read(s->pb, ps2buf, len);if (bytesread != len) {avio_skip(s->pb, len - bytesread);} else {uint8_t *p = 0;if (len >= 6)p = memchr(ps2buf, 'S', len - 5);if (p)m->sofdec = !memcmp(p+1, "ofdec", 5);m->sofdec -= !m->sofdec;if (m->sofdec < 0) {if (len == 980  && ps2buf[0] == 0) {/* PCI structure? */uint32_t startpts = AV_RB32(ps2buf + 0x0d);uint32_t endpts = AV_RB32(ps2buf + 0x11);uint8_t hours = ((ps2buf[0x19] >> 4) * 10) + (ps2buf[0x19] & 0x0f);uint8_t mins  = ((ps2buf[0x1a] >> 4) * 10) + (ps2buf[0x1a] & 0x0f);uint8_t secs  = ((ps2buf[0x1b] >> 4) * 10) + (ps2buf[0x1b] & 0x0f);m->dvd = (hours <= 23 &&mins  <= 59 &&secs  <= 59 &&(ps2buf[0x19] & 0x0f) < 10 &&(ps2buf[0x1a] & 0x0f) < 10 &&(ps2buf[0x1b] & 0x0f) < 10 &&endpts >= startpts);} else if (len == 1018 && ps2buf[0] == 1) {/* DSI structure? */uint8_t hours = ((ps2buf[0x1d] >> 4) * 10) + (ps2buf[0x1d] & 0x0f);uint8_t mins  = ((ps2buf[0x1e] >> 4) * 10) + (ps2buf[0x1e] & 0x0f);uint8_t secs  = ((ps2buf[0x1f] >> 4) * 10) + (ps2buf[0x1f] & 0x0f);m->dvd = (hours <= 23 &&mins  <= 59 &&secs  <= 59 &&(ps2buf[0x1d] & 0x0f) < 10 &&(ps2buf[0x1e] & 0x0f) < 10 &&(ps2buf[0x1f] & 0x0f) < 10);}}}av_free(ps2buf);/* If this isn't a DVD packet or no memory* could be allocated, just ignore it.* If we did, move back to the start of the* packet (plus 'length' field) */if (!m->dvd || avio_skip(s->pb, -(len + 2)) < 0) {/* Skip back failed.* This packet will be lost but that can't be helped* if we can't skip back*/goto redo;}} else {/* No memory */avio_skip(s->pb, len);goto redo;}} else if (!m->dvd) {int len = avio_rb16(s->pb);avio_skip(s->pb, len);goto redo;}}if (startcode == PROGRAM_STREAM_MAP) {mpegps_psm_parse(m, s->pb);goto redo;}/* find matching stream */if (!((startcode >= 0x1c0 && startcode <= 0x1df) ||(startcode >= 0x1e0 && startcode <= 0x1ef) ||(startcode == 0x1bd) ||(startcode == PRIVATE_STREAM_2) ||(startcode == 0x1fd)))goto redo;if (ppos) {*ppos = avio_tell(s->pb) - 4;}len = avio_rb16(s->pb);pts =dts = AV_NOPTS_VALUE;if (startcode != PRIVATE_STREAM_2){/* stuffing */for (;;) {if (len < 1)goto error_redo;c = avio_r8(s->pb);len--;/* XXX: for MPEG-1, should test only bit 7 */if (c != 0xff)break;}if ((c & 0xc0) == 0x40) {/* buffer scale & size */avio_r8(s->pb);c    = avio_r8(s->pb);len -= 2;}if ((c & 0xe0) == 0x20) {dts  =pts  = get_pts(s->pb, c);len -= 4;if (c & 0x10) {dts  = get_pts(s->pb, -1);len -= 5;}} else if ((c & 0xc0) == 0x80) {/* mpeg 2 PES */flags      = avio_r8(s->pb);header_len = avio_r8(s->pb);len       -= 2;if (header_len > len)goto error_redo;len -= header_len;if (flags & 0x80) {dts         = pts = get_pts(s->pb, -1);header_len -= 5;if (flags & 0x40) {dts         = get_pts(s->pb, -1);header_len -= 5;}}if (flags & 0x3f && header_len == 0) {flags &= 0xC0;av_log(s, AV_LOG_WARNING, "Further flags set but no bytes left\n");}if (flags & 0x01) { /* PES extension */pes_ext = avio_r8(s->pb);header_len--;/* Skip PES private data, program packet sequence counter* and P-STD buffer */skip  = (pes_ext >> 4) & 0xb;skip += skip & 0x9;if (pes_ext & 0x40 || skip > header_len) {av_log(s, AV_LOG_WARNING, "pes_ext %X is invalid\n", pes_ext);pes_ext = skip = 0;}avio_skip(s->pb, skip);header_len -= skip;if (pes_ext & 0x01) { /* PES extension 2 */ext2_len = avio_r8(s->pb);header_len--;if ((ext2_len & 0x7f) > 0) {id_ext = avio_r8(s->pb);if ((id_ext & 0x80) == 0)startcode = ((startcode & 0xff) << 8) | id_ext;header_len--;}}}if (header_len < 0)goto error_redo;avio_skip(s->pb, header_len);} else if (c != 0xf)goto redo;}if (startcode == PRIVATE_STREAM_1) {int ret = ffio_ensure_seekback(s->pb, 2);if (ret < 0)return ret;startcode = avio_r8(s->pb);m->raw_ac3 = 0;if (startcode == 0x0b) {if (avio_r8(s->pb) == 0x77) {startcode = 0x80;m->raw_ac3 = 1;avio_skip(s->pb, -2);} else {avio_skip(s->pb, -1);}} else {len--;}}if (len < 0)goto error_redo;if (dts != AV_NOPTS_VALUE && ppos) {int i;for (i = 0; i < s->nb_streams; i++) {if (startcode == s->streams[i]->id &&(s->pb->seekable & AVIO_SEEKABLE_NORMAL) /* index useless on streams anyway */) {ff_reduce_index(s, i);av_add_index_entry(s->streams[i], *ppos, dts, 0, 0,AVINDEX_KEYFRAME /* FIXME keyframe? */);}}}*pstart_code = startcode;*ppts        = pts;*pdts        = dts;return len;
}

该函数的作用就是:解析PS流中的一个PES packet,将其PES packet header里面的信息解析出来。

形参s:既是输入型参数也是输出型参数,指向一个AVFormatContext类型的变量。s->pb包含需要被解析的PS流的二进制数据。mpegps_read_pes_header函数解析的是s->pb->buf_ptr指向的PS流数据中的下一个PES packet。

形参ppos:输出型参数,*ppos为读取到的PES packet相对于文件首的偏移字节数。

形参pstart_code:输出型参数。如果读取到了PES packet,且该PES packet里面包含的不是的private_stream_1,*pstart_code为其PES packet header中的stream_id属性的值加0x100。

形参ppts:输出型参数。*ppts为从该PES packet的PES packet header中读取到的pts。

形参pdts:输出型参数。*pdts为从该PES packet的PES packet header中读取到的dts。

返回值:解析成功,返回该PES packet去掉PES packet header后的大小(即基本码流ES数据的大小)。解析失败,返回一个负数。

三、mpegps_read_pes_header函数的内部实现分析

mpegps_read_pes_header函数中,首先通过find_next_start_code函数找到s->pb->buf_ptr指向的PS流数据中的下一个pack header的起始码 或 下一个system header的起始码 或 下一个PES packet header的起始码:

redo:/* next start code (should be immediately after) */m->header_state = 0xff;size      = MAX_SYNC_SIZE;startcode = find_next_start_code(s->pb, &size, &m->header_state);last_sync = avio_tell(s->pb);if (startcode < 0) {if (avio_feof(s->pb))return AVERROR_EOF;// FIXME we should remember header_statereturn FFERROR_REDO;}

如果找到的是pack header的起始码 或 system header的起始码 或 找到的PES packet中包含padding_stream数据,通过goto语句跳转,然后重新通过find_next_start_code函数查找下一个起始码:

    if (startcode == PACK_START_CODE)goto redo;if (startcode == SYSTEM_HEADER_START_CODE)goto redo;if (startcode == PADDING_STREAM) {avio_skip(s->pb, avio_rb16(s->pb));goto redo;}

如果找到了符合要求的PES packet header的起始码,通过avio_tell函数读取该PES packet相对于文件首的偏移字节数,赋值给*ppos。关于avio_tell函数的用法可以参考:《FFmpeg源码:avio_tell函数分析》:

    /* find matching stream */if (!((startcode >= 0x1c0 && startcode <= 0x1df) ||(startcode >= 0x1e0 && startcode <= 0x1ef) ||(startcode == 0x1bd) ||(startcode == PRIVATE_STREAM_2) ||(startcode == 0x1fd)))goto redo;if (ppos) {*ppos = avio_tell(s->pb) - 4;}

读取PES packet header中的PES_packet_length属性,赋值给变量len。关于avio_rb16函数的用法可以参考:《FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》:

    len = avio_rb16(s->pb);

读取PES packet header中的PES_scrambling_control、PES_priority、data_alignment_indicator、copyright、original_or_copy属性,赋值给变量c:

        c = avio_r8(s->pb);

如果上述读取到的PES_packet_length属性后面的值为"10((c & 0xc0) == 0x80为真),表示读取到的PES packet header的格式正确:

 执行大括号内的内容:

else if ((c & 0xc0) == 0x80) {
//...
}

读取PTS_DTS_flags、ESCR_flag、ES_rate_flag、DSM_trick_mode_flag、additional_copy_info_flag、PES_CRC_flag、PES_extension_flag这7个属性,赋值给变量flags:

        flags      = avio_r8(s->pb);

读取PES_header_data_length属性,赋值给变量header_len:

        header_len = avio_r8(s->pb);

如果PTS_DTS_flags属性的值为'10',表示PES packet header中会存在PTS,读取PTS;值为'11'时,表示PES packet header中会同时存在PTS和DTS,读取PTS和DTS,分别赋值给变量dts和pts:

        if (flags & 0x80) {dts         = pts = get_pts(s->pb, -1);header_len -= 5;if (flags & 0x40) {dts         = get_pts(s->pb, -1);header_len -= 5;}}

如果PES_extension_flag属性的值为1,表示PES packet header有PES_extension域,读取PES_extension域:

        if (flags & 0x01) { /* PES extension */pes_ext = avio_r8(s->pb);header_len--;/* Skip PES private data, program packet sequence counter* and P-STD buffer */skip  = (pes_ext >> 4) & 0xb;skip += skip & 0x9;if (pes_ext & 0x40 || skip > header_len) {av_log(s, AV_LOG_WARNING, "pes_ext %X is invalid\n", pes_ext);pes_ext = skip = 0;}avio_skip(s->pb, skip);header_len -= skip;if (pes_ext & 0x01) { /* PES extension 2 */ext2_len = avio_r8(s->pb);header_len--;if ((ext2_len & 0x7f) > 0) {id_ext = avio_r8(s->pb);if ((id_ext & 0x80) == 0)startcode = ((startcode & 0xff) << 8) | id_ext;header_len--;}}}

最后返回从该PES packet的PES packet header中读取到的pts、dts、该PES packet去掉PES packet header后的大小等信息:

    *pstart_code = startcode;*ppts        = pts;*pdts        = dts;return len;

四、FFmpeg源码中,解析TS流中的PES流的实现

FFmpeg源码中,解析TS流中的PES流所使用的函数不一样,是通过mpegts_push_data函数进行解析的,具体可以参考:《音视频入门基础:MPEG2-TS专题(19)——FFmpeg源码中,解析TS流中的PES流的实现》。

相关文章:

音视频入门基础:MPEG2-PS专题(5)——FFmpeg源码中,解析PS流中的PES流的实现

一、引言 从《音视频入门基础&#xff1a;MPEG2-PS专题&#xff08;3&#xff09;——MPEG2-PS格式简介》中可以知道&#xff0c;PS流由一个个pack&#xff08;包装&#xff09;组成。一个pack 一个pack_header 一个或多个PES_packet。pack_header中还可能存在system header…...

【问题记录】npm create vue@latest报错

1&#xff0c;错误日志 npm error code EPERM npm error syscall mkdir npm error path D:\Program Files\nodejs\node_cache\_cacache npm error errno EPERM npm error FetchError: Invalid response body while trying to fetch https://registry.npmjs.org/create-vue: EP…...

OpenGL材质系统和贴图纹理

上一篇文章当中笔者为大家介绍了风氏光照模型&#xff0c;相信大家也发现了光照着色器当中有设置有很多控制光照强度的参数&#xff0c;而所谓的材质系统就是我们可以人为的去调节这些参数&#xff0c;让一个物体的反光效果能够更加接近我们现实生活当中的一些物体。 材质系统…...

Markdown中类图的用法

Markdown中类图的用法 前言语法详解基本流程图几何图形节点默认的节点分离节点的ID与内容节点形状圆角形节点的语法圆形节点的语法。非对称节点语法菱形节点的语法。六角形节点的语法。平行四边形节点的语法。梯形节点的语法。 连接线基本的连接线语法。无向线段连接线。点状连…...

钓鱼攻击(Phishing)详解和实现 (网络安全)

钓鱼攻击&#xff08;Phishing&#xff09;详解和实现 钓鱼攻击是一种社会工程学攻击&#xff0c;攻击者通过伪装成可信任的实体诱使受害者泄露敏感信息&#xff0c;如用户名、密码、信用卡号等。以下详细介绍钓鱼攻击的原理、种类、实现方式&#xff0c;以及防御措施。 一、钓…...

window11 wsl mysql8 错误分析:1698 - Access denied for user ‘root‘@‘kong.mshome.net‘

&#x1f6a8; 错误分析&#xff1a;1698 - Access denied for user rootkong.mshome.net 这个错误是因为 MySQL 的 root 用户 使用 auth_socket 插件进行身份验证&#xff0c;而不是使用密码。因此&#xff0c;当你尝试从 远程主机 连接时&#xff0c;MySQL 会拒绝访问。 ✅ …...

C++线程同步之条件变量

C线程同步之条件变量 文章目录 C线程同步之条件变量什么是条件变量&#xff08;Condition Variable&#xff09;&#xff1f;条件变量的主要用途常见的应用场景C11中的条件变量condition_variable的使用方法std::condition_variable的使用步骤典型的使用示例&#xff1a;生产者…...

如何实现多条件搜索

我们先来看多条件查询的样式是什么样的&#xff01; 给查询按钮添加点击事件&#xff0c;然后获取到对应输入框中的值 然后通过filter过滤&#xff0c;对获取到的数据进行筛选 &#xff0c;然后调用渲染函数将过滤搜索到的数据在页面中显示出来。 这就是进行多条件搜索出来的效…...

深入MySQL复杂查询优化技巧

在上一篇文章中&#xff0c;我们介绍了 MySQL 的关联关系理论与基础实践。本篇文章将进一步探讨 MySQL 复杂查询的优化技巧&#xff0c;帮助开发者应对大型数据集和高并发场景中的性能挑战。我们将涵盖索引设计、查询计划分析、分区技术以及事务管理的优化。 一、索引优化 索引…...

Fabric环境部署-Git和Node安装

一.安装Git&#xff08;v2.43.0&#xff09; Git 是一个开源的分布式版本管理系统&#xff08;也是全球最大的开源软件存储服务器&#xff09;&#xff0c;用于敏捷高效地处理任何或小或大的项目。搭建区块链需要使用Git&#xff0c;因为区块链的开发和部署需要使用版本控制工…...

如何弥补开源大语言模型解决推理任务的不足

在实际应用中&#xff0c;大语言模型&#xff08;LLM&#xff09;可以通过与其他专门的推理技术结合&#xff0c;克服其在严格逻辑推理、深度推理或因果推理领域的不足。以下是几种有效的结合方式&#xff0c;分别从不同角度解决LLM在推理中的局限性。 一、结合符号推理系统 …...

Ubuntu 下载安装 Consul1.17.1

下载 wget https://releases.hashicorp.com/consul/1.17.1/consul_1.17.1_linux_amd64.zip解压&#xff1a; unzip -d consul_1.17.1_linux_amd64.zip /opt/module将解压出的二进制文件移动到 /usr/local/bin 目录中以便在系统中全局使用&#xff1a; sudo mv consul /usr/l…...

【数据库系统概论】并发控制--复习

1. 并发控制概述 并发控制是数据库系统处理多个事务同时执行时&#xff0c;保证数据一致性和事务隔离性的关键技术。 1.1并发操作的特点 数据库系统允许多个用户并发访问。典型应用场景&#xff1a; 飞机订票系统银行数据库系统网上购物系统 1.2并发操作可能带来的问题 并…...

MySQL(六)MySQL 案例

1. MySQL 案例 1.1. 设计数据库 1、首先根据相关业务需求(主要参考输出输入条件)规划出表的基本结构   2、根据业务规则进行状态字段设计   3、预估相关表的数据量进行容量规划   4、确定主键   5、根据对相关处理语句的分析对数据结构进行相应的变更。   设计表的时…...

DDcGAN_多分辨率图像融合的双鉴别条件生成对抗网络_y译文马佳义

摘要&#xff1a; 在本文中&#xff0c;我们提出了一种新的端到端模型&#xff0c;称为双鉴别条件生成对抗网络&#xff08;DDcGAN&#xff09;&#xff0c;用于融合不同分辨率的红外和可见光图像。我们的方法建立了一个生成器和两个鉴别器之间的对抗博弈。生成器的目的是基于特…...

[读书日志]从零开始学习Chisel 第一篇:书籍介绍,Scala与Chisel概述,Scala安装运行(敏捷硬件开发语言Chisel与数字系统设计)

简介&#xff1a;从20世纪90年代开始&#xff0c;利用硬件描述语言和综合技术设计实现复杂数字系统的方法已经在集成电路设计领域得到普及。随着集成电路集成度的不断提高&#xff0c;传统硬件描述语言和设计方法的开发效率低下的问题越来越明显。近年来逐渐崭露头角的敏捷化设…...

二、用例图

二、用例图 (一&#xff09;、用例图的基本概念 1、用例图的定义&#xff1a; 用例图是表示一个系统中用例与参与者关系之间的图。它描述了系统中相关的用户和系统对不同用户提供的功能和服务。 用例图相当于从用户的视角来描述和建模整个系统&#xff0c;分析系统的功能与…...

LWIP之一:使用STM32CubeMX搭建基于FreeRTOS的LWIP工程并分析协议栈初始化过程

工程搭建及LWIP协议栈初始化过程 一、使用STM32CubeMX快速生成工程二、修改测试三、LWIP协议栈初始化过程分析3.1 tcpip_init()3.1.1 lwip_init()3.1.1.1 sys_init()3.1.1.2 mem_init()3.1.1.3 memp_init()3.1.1.4 netif_init()3.1.1.5 udp_init()3.1.1.6 tcp_init()3.1.1.7 ig…...

个性化电影推荐系统|Java|SSM|JSP|

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、JSP、jquery,html 5⃣️数据库可…...

UE5AI感知组件

官方解释&#xff1a; AI感知系统为Pawn提供了一种从环境中接收数据的方式&#xff0c;例如噪音的来源、AI是否遭到破坏、或AI是否看到了什么。 AI感知组件&#xff08;AIPerception Component&#xff09;是用于实现游戏中的非玩家角色&#xff08;NPC&#xff09;对环境和其…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...