鸿蒙应用开发学习:使用视频播放(Video)组件播放视频和音频文件

一、前言

播放音视频是手机的重要功能之一,近期我学习了在鸿蒙系统应用开发中实现音视频的播放功能,应用中使用到了视频播放(Video)组件,@ohos.file.picker(选择器)。特撰此文分享一下我的学习经历。

二、参考资料

本次开发学习,主要参考了以下三个资料。

1.鸿蒙arkts的video组件简单使用示例

第一个资料是在CSDN社区里搜到了一篇简绍Video组件使用的博文,有现成的代码示例,一开始我就是参考了这篇博文的代码开始了视频和音频播放的学习。

2.官方开发文档(3.1/4.0)-指南-UI开发-添加组件-视频播放(Video)

第二个资料是官方网站内Video组件的指南,关于如何实现播放手机内的视频和音频相关知识,我参考了这个指南中的一些内容。

3.官方开发文档(3.1/4.0)-API参考-ArkTS接口参考-文件管理-@ohos.file.picker(选择器)

第三个资料是官方网站提供的文件管理器接口资料,获取手机内的视频的路径和音频文件路径的方法来自于这篇文档。

三、实现过程

1.先照着别人的教程试着做

最开始,我参照第一个链接中的内容,做了一个播放视频和音频的简单应用,利用Tabs组件制作了几个页面。其中标题为视频1和视频2的页面是参考了示例代码制作的,这两个页面都是播放的网络视频,视频地址来自于第一个链接提供的代码,在真机上测试可以正常播放。

第一个页面的初始状态,带自定义的控制器,播放的网络视频,后面又做了改动

第二个页面是Video组件的基础使用方法,播放的网络视频。

第二个页面的代码

TabContent() {Column({ space: 20 }) {Text("播放网络视频").fontSize(14).textAlign(TextAlign.Center).width('100%')Video({src: "https://vd3.bdstatic.com/mda-pmj5ajqd7p4b6pgb/576p/h264/1703044058699262355/mda-pmj5ajqd7p4b6pgb.mp4?auth_key=1703138418-0-0-618ea72b33be241c96c6cff86c06e080&bcevod_channel=searchbox_feed&cr=1&cd=0&pd=1&pt=4&logid=0018430194&vid=9762003448174112444&abtest=all"}).width("100%").aspectRatio(1.3)}}.tabBar("视频2")

第三个页面计划实现播放本地视频功能,但由于我没有示例中对应的视频资源,就没有照着做,需要另外想办法。第四个页面要实现播放音频功能,示例中给的音频链接不能正常访问,也无法正常播放,也需要另外想办法。

2.对播放本地视频的页面进行改进

参考资料中第一个链接里的示例中播放本地视频部分,需要将视频文件保存到项目的resources/rawfile文件夹下,等于是将视频写死了,而我希望通过浏览手机文件进行选择,实现播放手机内的任一本地视频的目的,因此需要对软件进行改进。

首先,我对参考资料中第二个链接的内容进行了学习,该文档中关于加载本地视频的知识除了使用资源访问符$rawfile()引用视频资源方法外,还有通Data Ability提供的视频路径频路径访问本地视频的方法。但这个Data Ability组件,我看了相关资料也没弄明白怎么用。让此事进入了死胡同。

但我之前学习了显示手机上的图片功能时,参考过一篇博文“【鸿蒙应用ArkTS开发系列】- 选择图片、文件和拍照功能实现”。这篇博文介绍了从手机相册和手机文件夹中选择图片,并在应用中显示的方法。我照着实现了该功能。该应用在显示图片信息的同时还会显示图片文件的Uri信息。我利用该功能从相册里选中视频文件后,虽然不能在应用中显示视频,但是视频的Uri信息确显示了出来。

手机上的视频文件

通过上述的显示手机图片的应用,从相册内选择视频文件

虽然视频文件没有显示出来,但文件Uri还是提供出来了(这是另外一个显示图片的应用)。

我尝试将Uri信息写入我的本地视频播放代码中,在真机上测试,可以播放(见下图)。

关键代码

Video({src:  "datashare:///media/video/914"   // 手机视频的路径}).width("100%").aspectRatio(1.3)

通过上面的测试,说明只要获取到视频文件的Uri信息,并将其赋值给Video组件的src参数后,就可以播放了。接下来的开发步骤就是实现获取视频文件的Uri并赋值给Video组件的src参数。

我通过对官方文档的研究发现,使用@ohos.file.picker (选择器)可以实现对视频和音频文件的选择(相关内容见第三个链接)。其中选择视频文件要用到PhotoViewPicker。并且文档中有相应的示例代码。我对自己的应用进行了如下修改:

2.1 修改显示页面,添加了“选择文件”按钮组件和显示视频文件Uri的文本组件

TabContent() {Column({ space: 20 }) {Text("播放手机视频").fontSize(14).textAlign(TextAlign.Center).width('100%')Video({src: this.videoUri // 手机上的视频文件路径}).width("100%").aspectRatio(1.3)Button("选择文件").onClick(() => {this.VideoPicker() // 自定义函数获取本地视频路径})Text("文件路径" + this.videoUri) // 显示视频文件路径.fontSize(14).textAlign(TextAlign.Center).width('100%')}
}.tabBar("视频3")

2.2 添加了使用PhotoViewPicker选择器获取视频文件路径的函数

async VideoPicker() {try {let PhotoSelectOptions = new picker.PhotoSelectOptions();PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.VIDEO_TYPE; // 文件类型 .IMAGE_TYPEPhotoSelectOptions.maxSelectNumber = 1; // 可选择的文件数量let photoPicker = new picker.PhotoViewPicker();photoPicker.select(PhotoSelectOptions).then((PhotoSelectResult) => {console.info('PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(PhotoSelectResult));this.videoUri = PhotoSelectResult.photoUris[0] // 获取视频文件路径}).catch((err) => {console.error('PhotoViewPicker.select failed with err: ' + err);});} catch (err) {console.error('PhotoViewPicker failed with err: ' + err);}
}

修改代码后,就可以选择手机上的视频文件进行播放了。

3.对播放音频的页面进行改进

由于参考资料中第一个链接提供的网络音频文件不能正常访问,因此,我决定将播放音频的页面修改为播放本地音频。从手机获取上获取音频文件路径的方法是通过@ohos.file.picker (选择器)的AudioViewPicker来实现的,链接三的文档中同样有示例代码,复制过来稍加修改就可以了。

3.1页面代码修改如下

TabContent() {Column({ space: 20 }) {Text("播放手机音频").fontSize(14).textAlign(TextAlign.Center).width('100%')Video({src: this.audioUri // 手机上的音频文件路径}).width('100%').aspectRatio(1.3)Button("选择文件").onClick(() => {this.AudioPicker() // 自定义函数获取本地音频路径})Text("文件路径" + this.audioUri) // 显示音频文件路径.fontSize(14).textAlign(TextAlign.Center).width('100%')}
}.tabBar("音频")

3.2 添加了使用PhotoViewPicker选择器选择视频文件的函数

async AudioPicker() {try {let AudioSelectOptions = new picker.AudioSelectOptions();let audioPicker = new picker.AudioViewPicker();audioPicker.select(AudioSelectOptions).then((AudioSelectResult) => {console.info('AudioViewPicker.select successfully, AudioSelectResult uri: ' + JSON.stringify(AudioSelectResult));this.audioUri = AudioSelectResult[0] // 获取音频文件路径}).catch((err) => {console.error('AudioViewPicker.select failed with err: ' + err);});} catch (err) {console.error('AudioViewPicker failed with err: ' + err);}
}

修改代码后,就可以对手机上的音频文件进行选择和播放了(见下图)。

4.对第一个页面的进行调整

在观看了官网上关于Video组件的文档资料后(链接二),我又尝试着对第一个页面内进行了一些修改,主要是取消了默认控制器的显示;增加了显示播放进度的组件;调整了页面布局;将“移动进度到”按钮对应的进度参数从固定值“30”,改为了变量,增加了文本输入框,修改这个变量,使得该功能更灵活。

代码修改后:

TabContent() {Column({ space: 20 }) {Text("播放网络视频-带控制器").fontSize(14).textAlign(TextAlign.Center).width('100%')Video({controller: this.controller,currentProgressRate: this.speed,src: "https://vd4.bdstatic.com/mda-phegibuu9ba9nrpe/hd/cae_h264/1692171953345322752/mda-phegibuu9ba9nrpe.mp4?auth_key=1703502162-0-0-6fd1550e28060e61121ed6d2a773b68c&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=0162714477&vid=1316841417577475878&abtest=all"}).controls(false).width("100%").aspectRatio(1.4).onPrepared((event) => {this.durationTime = event.duration // 获取视频总时长}).onUpdate((event) => {this.currentTime = event.time // 视频播放时获取当前时间})// 播放进度显示Row() {Text(JSON.stringify(this.currentTime) + 's') // 文本形式显示播放进度// 滑动条形式显示播放进度Slider({value: this.currentTime,min: 0,max: this.durationTime}).onChange((value: number, mode: SliderChangeMode) => {this.controller.setCurrentTime(value);}).layoutWeight(1)Text(JSON.stringify(this.durationTime) + 's')}.width("100%")// 播放倍速滑动选择Row() {Slider({value: this.speed,min: 0.75,step: 0.25,max: 2,style: SliderStyle.InSet}).layoutWeight(1).showSteps(true).onChange(value => {this.speed = value})Text(this.speed + "倍速") // 文本形式显示播放速率信息.fontSize(14).textAlign(TextAlign.Center)}.width("100%")// 自定义播放控制器Row({ space: 10 }) {Button("播放").onClick(() => {this.controller.start()})Button("暂停").onClick(() => {this.controller.pause()})Button("结束").onClick(() => {this.controller.stop()})}// 移动进度控制Row({ space: 10 }) {Button("移动进度到").onClick(() => {this.controller.setCurrentTime(parseInt(this.jumpToTime)) // 设置视频控制器当前时间,单位为秒})TextInput({ text: this.jumpToTime }).layoutWeight(1) // 文本输入框,输入要移动到的进度位置.onChange((value: string) => {this.jumpToTime = value})Text("s")}.width('60%')}.width('100%')
}.tabBar("视频1")
5.遗留的三个问题

开发学习到这里,让我对Video组件和@ohos.file.picker (选择器)有了一些基础的了解,但现在还存在三个问题需要在之后的学习中去解决。

第一个问题是播放网络视频都是在代码中写死了的。原因是,我暂时还没有办法从视频网站上获取到视频文件的网络地址,只能先按着别人的教程做,目的是验证功能,没有什么实际意义。

第二个问题是点击Video组件默认控制器右侧的全屏按钮后视频放大,如果视频是横屏,手机不会自动以横屏显示视频,且视频显示显示不全(如下图),对此,暂时没有找到相关的解决办法。


全屏显示后异常

第三个问题是经过测试发现,当前的代码不适合播放竖屏形式的视频,原因是Video组件内设置的长宽比为1.3或1.4(即横屏)。如果视频是竖屏形式的,在Video组件中播放时,组件会将视频上部和下部裁切掉,只保留中间的部分。

这三个问题,希望能在不久的将来找到解决方法,使得视频播放功能更实用。先记录在这里以备忘。

四、代码展示

最后展示本次学习的成果

import picker from '@ohos.file.picker';@Entry
@Component
struct EasyVideoPlayPage {@State videoUri: string = "" // 视频文件地址@State audioUri: string = "" // 音频文件地址@State speed: number = 1 // 视频播放速率,默认为1倍速@State currentTime: number = 0; // 当前时间(秒)@State durationTime: number = 0; // 视频总时长(秒)@State jumpToTime: string = "0"; // 移动进度到(秒)controller: VideoController = new VideoController() // 视频控制器build() {Column() {Tabs() {TabContent() {Column({ space: 20 }) {Text("播放网络视频-带控制器").fontSize(14).textAlign(TextAlign.Center).width('100%')Video({controller: this.controller,currentProgressRate: this.speed,src: "https://vd4.bdstatic.com/mda-phegibuu9ba9nrpe/hd/cae_h264/1692171953345322752/mda-phegibuu9ba9nrpe.mp4?auth_key=1703502162-0-0-6fd1550e28060e61121ed6d2a773b68c&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=0162714477&vid=1316841417577475878&abtest=all"}).controls(false).width("100%").aspectRatio(1.4).onPrepared((event) => {this.durationTime = event.duration // 获取视频总时长}).onUpdate((event) => {this.currentTime = event.time // 视频播放时获取当前时间})// 播放进度显示Row() {Text(JSON.stringify(this.currentTime) + 's') // 文本形式显示播放进度// 滑动条形式显示播放进度Slider({value: this.currentTime,min: 0,max: this.durationTime}).onChange((value: number, mode: SliderChangeMode) => {this.controller.setCurrentTime(value);}).layoutWeight(1)Text(JSON.stringify(this.durationTime) + 's')}.width("100%")// 播放倍速滑动选择Row() {Slider({value: this.speed,min: 0.75,step: 0.25,max: 2,style: SliderStyle.InSet}).layoutWeight(1).showSteps(true).onChange(value => {this.speed = value})Text(this.speed + "倍速") // 文本形式显示播放速率信息.fontSize(14).textAlign(TextAlign.Center)}.width("100%")// 自定义播放控制器Row({ space: 10 }) {Button("播放").onClick(() => {this.controller.start()})Button("暂停").onClick(() => {this.controller.pause()})Button("结束").onClick(() => {this.controller.stop()})}// 移动进度控制Row({ space: 10 }) {Button("移动进度到").onClick(() => {this.controller.setCurrentTime(parseInt(this.jumpToTime)) // 设置视频控制器当前时间,单位为秒})TextInput({ text: this.jumpToTime }).layoutWeight(1) // 文本输入框,输入要移动到的进度位置.onChange((value: string) => {this.jumpToTime = value})Text("s")}.width('60%')}.width('100%')}.tabBar("视频1")TabContent() {Column({ space: 20 }) {Text("播放网络视频").fontSize(14).textAlign(TextAlign.Center).width('100%')Video({src: "https://vd3.bdstatic.com/mda-pmj5ajqd7p4b6pgb/576p/h264/1703044058699262355/mda-pmj5ajqd7p4b6pgb.mp4?auth_key=1703138418-0-0-618ea72b33be241c96c6cff86c06e080&bcevod_channel=searchbox_feed&cr=1&cd=0&pd=1&pt=4&logid=0018430194&vid=9762003448174112444&abtest=all"}).width("100%").aspectRatio(1.3)}}.tabBar("视频2")TabContent() {Column({ space: 20 }) {Text("播放手机视频").fontSize(14).textAlign(TextAlign.Center).width('100%')Video({src: this.videoUri // 手机上的视频文件路径}).width("100%").aspectRatio(1.3)Button("选择文件").onClick(() => {this.VideoPicker() // 自定义函数获取本地视频路径})Text("文件路径" + this.videoUri) // 显示视频文件路径.fontSize(14).textAlign(TextAlign.Center).width('100%')}}.tabBar("视频3")TabContent() {Column({ space: 20 }) {Text("播放手机音频").fontSize(14).textAlign(TextAlign.Center).width('100%')Video({src: this.audioUri // 手机上的音频文件路径}).width('100%').aspectRatio(1.3)Button("选择文件").onClick(() => {this.AudioPicker() // 自定义函数获取本地音频路径})Text("文件路径" + this.audioUri) // 显示音频文件路径.fontSize(14).textAlign(TextAlign.Center).width('100%')}}.tabBar("音频")}.animationDuration(500)}.justifyContent(FlexAlign.Start).width('100%').backgroundColor('#EAEAEA').padding(10) // 根 Column -end}// 视频文件选择器:代码来源于官方开发文档-API参考-ArkTS接口参考-文件管理-@ohos.file.picker-PhotoViewPicker,对源码进行了修改async VideoPicker() {try {let PhotoSelectOptions = new picker.PhotoSelectOptions();PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.VIDEO_TYPE; // 文件类型 .IMAGE_TYPEPhotoSelectOptions.maxSelectNumber = 1; // 可选择的文件数量let photoPicker = new picker.PhotoViewPicker();photoPicker.select(PhotoSelectOptions).then((PhotoSelectResult) => {console.info('PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(PhotoSelectResult));console.info('PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(PhotoSelectResult.photoUris[0]));this.videoUri = PhotoSelectResult.photoUris[0] // 获取视频文件路径}).catch((err) => {console.error('PhotoViewPicker.select failed with err: ' + err);});} catch (err) {console.error('PhotoViewPicker failed with err: ' + err);}}// 音频文件选择器:代码来源于官方开发文档-API参考-ArkTS接口参考-文件管理-@ohos.file.picker-AudioViewPicker,对源码进行了修改async AudioPicker() {try {let AudioSelectOptions = new picker.AudioSelectOptions();let audioPicker = new picker.AudioViewPicker();audioPicker.select(AudioSelectOptions).then((AudioSelectResult) => {console.info('AudioViewPicker.select successfully, AudioSelectResult uri: ' + JSON.stringify(AudioSelectResult));this.audioUri = AudioSelectResult[0] // 获取音频文件路径}).catch((err) => {console.error('AudioViewPicker.select failed with err: ' + err);});} catch (err) {console.error('AudioViewPicker failed with err: ' + err);}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【设计】基于web的会员管理系统

1、引言 设计结课作业,课程设计无处下手,网页要求的总数量太多?没有合适的模板?数据库,java,python,vue,html作业复杂工程量过大?毕设毫无头绪等等一系列问题。你想要解决的问题&am…

Elasticsearch 单节点部署教程,以及踩坑记录

1、简介 Elasticsearch 作为分布式搜索引擎,在生产环境中使用集群部署,对于学习者而言我们只需要掌握如何使用即可,后续更高级的集群部署配置将在以后博客中更新。 Elasticsearch 更新迭代速度非常快,并且不同版本有着很大区别&am…

外包干了30天,技术明显退步。。

🍅 视频学习:文末有免费的配套视频可观看 🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 这次来聊一个大家可能也比较关心的问题,那就是就业城市选择的问题。而谈到这个问题&a…

scrapy的基本使用介绍

创建项目 ### 1. 创建虚拟环境 conda create -n spiderScrapy python3.9 ### 2. 安装scrapy pip install scrapy2.8.0 -i https://pypi.tuna.tsinghua.edu.cn/simple### 3. 生成一个框架 scrapy startproject my_spider### 4. 生成项目 scrapy genspider baidu https://www.b…

基于springboot+vue实现高校学生党员发展管理系统项目【项目源码+论文说明】

基于springboot实现高校学生党员发展管理系统演示 摘要 随着高校学生规模的不断扩大,高校内的党员统计及发展管理工作面临较大的压力,高校信息化建设的不断优化发展也进一步促进了系统平台的应用,借助系统平台可以实现更加高效便捷的党员信息…

吴恩达机器学习-可选实验:使用ScikitLearn进行线性回归(Linear Regression using Scikit-Learn)

文章目录 实验一目标工具梯度下降加载数据集缩放/规范化训练数据创建并拟合回归模型查看参数作出预测绘制结果 恭喜 实验二目标工具线性回归,闭式解加载数据集创建并拟合模型查看参数作出预测 第二个例子恭喜 有一个开源的、商业上可用的机器学习工具包,…

2024蓝桥杯每日一题(双指针)

一、第一题:牛的学术圈 解题思路:双指针贪心 仔细思考可以知道,写一篇综述最多在原来的H指数的基础上1,所以基本方法可以是先求出原始的H指数,然后分类讨论怎么样提升H指数。 【Python程序代码】 n,l map(int,…

GO: 快速升级Go版本

由于底层依赖升级了,那我们也要跟着升,go老版本已经不足满足需求了,必须要将版本升级到1.22.0以上 查看当前Go版本 命令查看go版本 go version[rootlocalhost local]# go version go version go1.21.4 linux/amd64 [rootlocalhost local]# …

一篇文章带你了解Python数据分析

目录 一、什么是数据分析? 二、为什么学习数据分析? 三、数据分析实现流程 一、什么是数据分析? 是把隐藏在一些看似杂乱无章的数据背后的信息提炼出来,总结出所研究对象的内在规律。 使得数据的价值最大化 指定促销活动的方…

【网络原理】使用Java基于UDP实现简单客户端与服务器通信

目录 🎄API介绍🌸DatagramSocket🌸DatagramPacket🌸InetSocketAddress 🌳回显客户端与服务器🌸建立回显服务器🌸回显客户端 ⭕总结 我们用Java实现UDP数据报套接字编程,需要借用以下…

yolo模型中神经节点Mul与Sigmoid 和 Conv、Concat、Add、Resize、Reshape、Transpose、Split

yolo模型中神经节点Mul与Sigmoid 和 Conv、Concat、Add、Resize、Reshape、Transpose、Split 在YOLO(You Only Look Once)模型中,具体作用和用途的解释:

Claude 3 Sonnet 模型现已在亚马逊云科技的 Amazon Bedrock 正式可用!

今天,我们宣布一个激动人心的里程碑:Anthropic 的 Claude 3 Sonnet 模型现已在亚马逊云科技的 Amazon Bedrock 正式可用。 下一代 Claude (Claude 3) 的三个模型 Claude 3 Opus、Claude 3 Sonnet 和 Claude 3 Haiku 将陆续登陆 Amazon Bedrock。Amazon …

二叉树遍历(前中后序的递归/非递归遍历、层序遍历)

二叉树的遍历 1. 二叉树的前序、中序、后序遍历 前、中、后序遍历又叫深度优先遍历 注:严格来说,深度优先遍历是先访问当前节点再继续递归访问,因此,只有前序遍历是严格意义上的深度优先遍历 首先需要知道下面几点: …

【排序】详解插入排序

一、思想 插入排序是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。具体步骤如下,将数组下标为0的元素视为已经排序的部分,从1开始遍历数组,在遍历的过程中当前元素从…

upload-labs通关记录

文章目录 前言 1.pass-012.pass-023.pass-034.pass-045.pass-056.pass-067.pass-078.pass-089.pass-0910.pass-1011.pass-1112.pass-1213.pass-1314.pass-1415.pass-1516.pass-1617.pass-1718.pass-1819.pass-19 前言 本篇文章记录upload-labs中,所有的通过技巧和各…

蓝桥杯python常用内置函数

一、 abs() #返回数字的绝对值 例: 二、 all() #判断给定的可迭代参数中的所有元素是否都为True,若是则返回True,反之返回False 例: 三、 any() #判断给定的可迭代参数是否都为False,全为False则返回False&am…

SSL 证书,了解一下常识

公司的网站、应用怎么才能保证在互联网上安全运行,不被攻击、盗取数据呢? 创业必经之路,一步一步走就对了,可能没赶上红利期,但不做就等于0。 概述 SSL 证书(SSL Certificates)又称数字证书&am…

leetcode 1143. 最长公共子序列【动态规划】

leetcode 1143. 最长公共子序列 int longestCommonSubsequence(char* text1, char* text2) {int len1 strlen(text1);int len2 strlen(text2);int dp[len1 1][len2 1];memset(dp, 0, sizeof(dp));for (int i 1; i < len1; i) {for (int j 1; j < len2; j) {if (t…

【vue2基础教程】vue指令

文章目录 前言一、内容渲染指令1.1 v-text1.2 v-html1.3 v-show1.4 v-if1.5 v-else 与 v-else-if 二、事件绑定指令三、属性绑定指令总结 前言 Vue.js 是一款流行的 JavaScript 框架&#xff0c;广泛应用于构建交互性强、响应速度快的现代 Web 应用程序。Vue 指令是 Vue.js 中…

IPsec VPN之安全联盟

一、何为安全联盟 IPsec在两个端点建立安全通信&#xff0c;此时这两个端点被称为IPsec对等体。安全联盟&#xff0c;即SA&#xff0c;是指通信对等体之间对某些要素的约定&#xff0c;定义了两个对等体之间要用何种安全协议、IP报文的封装方式、加密和验证算法。SA是IPsec的基…