Android OpenGL ES 学习(十一) –渲染YUV视频以及视频抖音特效

news2024/12/26 11:28:46

OpenGL 学习教程
Android OpenGL ES 学习(一) – 基本概念
Android OpenGL ES 学习(二) – 图形渲染管线和GLSL
Android OpenGL ES 学习(三) – 绘制平面图形
Android OpenGL ES 学习(四) – 正交投影
Android OpenGL ES 学习(五) – 渐变色
Android OpenGL ES 学习(六) – 使用 VBO、VAO 和 EBO/IBO 优化程序
Android OpenGL ES 学习(七) – 纹理
Android OpenGL ES 学习(八) –矩阵变换
Android OpenGL ES 学习(九) – 坐标系统和。实现3D效果
Android OpenGL ES 学习(十) – GLSurfaceView 源码解析GL线程以及自定义 EGL
Android OpenGL ES 学习(十一) –渲染YUV视频以及视频抖音特效

代码工程地址: https://github.com/LillteZheng/OpenGLDemo.git

之前学习到的图片,这一章,我们使用OpenGL 来解析 yuv 视频,并实现一些效果,废话不多说,先上效果:
在这里插入图片描述
在这里插入图片描述
当然,在进入主题之前,先学习一些基础知识。

一. 什么是 YUV

在说 yuv 之前,就不得不说 RGB 图像空间,顾名思义,RGB 是值图像的每一个像素都有 R、G,B 三个值,且三个值一次排列存储;但不一定说一定是按照 R,G,B 顺序排列,也可以是 B,G,R 这样的顺序。其中 R,G,B 的位深为 8 bit。
在这里插入图片描述

我们常见的图片处理,都是用 R,G,B 的图像格式,比如bitmap,比如图像的存储,基本使用 R,G,B

1.1 那为什么还有 yuv 呢?

我们知道,视频是由一张张图片组成,假设有一个 1920 * 1080 分辨率、帧率为60帧的视频,如果不进行压缩处理,并且使用RGB进行存储的话,仅仅一分钟的视频就能达到 ( 1920 * 1080 * 8 * 60 * 60 )bit (约等于56G),这显然是很夸张的。
但R,G,B这三个颜色是彼此是由相关性的,不利于编码压缩,所以,我们需要另外一种图像格式,来解决图像压缩问题,这个时候,yuv 就被提升来了。

yuv 图像格式将亮度信息 Y 和 色彩信息 UV 分离开来,Y 表示亮度,是图像的总体轮廓,即我们常说的灰度值,UV 表示色度,主要描绘图像的色彩信息,即颜色饱和度。如下图(图片来源wiki百科):

在这里插入图片描述

yuv 最早用于电视系统和模拟视频领域,它兼容了黑白电视和彩色电视,如果你家有vcd,dvd 这种设备,就会发现有 YCbCr(YUV) 这种接口,如果是黑白电视,值需要接入Y分量即可。

从很早的时候,人们就发现,人类对亮度信息比较敏感,而对色彩信息不那么敏感,比如我们降低一些颜色值,并不影响人对这张图像感官。因此,yuv 的编码压缩,又可以分为 YUV 4:4:4、YUV 4:2:2、YUV 4:2:0 这几种常用的类型

1.2 YUV 格式

YUV 4:4:4、YUV 4:2:2、YUV 4:2:0,指的是U,V 分量像素点的个数和采集方式,其中又以 YUV 4:2:0 最为常用。

在这里插入图片描述
可以这样简单理解:

  • YUV 4:4:4:每一个 Y 就对应一个 U 和一个 V分量
  • YUV 4:2:2:每两个 Y 共用一个 U、一个 V 分量
  • YUV 4:2:0:每四个 Y 共用一个 U、V分量

如下图(图片来源极客时间):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

其中,YUV 又有不同的存储方式:

  • packed :packed格式是先连续存储所有的Y分量,然后依次交叉储存U、V分量;
  • planar:planar格式也会先连续存储所有的Y分量,但planar会先连续存储U分量的数据,再连续存储V分量的数据,或者先连续存储V分量的数据,再连续存储U分量的数据:

在这里插入图片描述

更多内容和YUV转RGB,可以参考:https://time.geekbang.org/column/article/449795

二. 视频解析

从之前OpenGL 的纹理教程中,我们是把一张图片,通过纹理的方式,传递给片段着色器,最终通过纹理采样,复制给片段颜色值,呈现出来的。
现在使用 YUV ,该如何处理呢?我们知道,视频最终的呈现还是RGB格式的数据,因此,我们需要把 YUV 的数据,所以需要在片段着色器赋值之前,把YUV转换成 RGB。

2.1 GL_LUMINANCE

在OpenGL 的api 中,可以发现有个 GL_LUMINANCE 格式,它表示只取一个颜色通道,这样的话,就可以把 YUV 拆分成3个通道来读取,然后我们设置 3个纹理,把 YUV 数据传入其中,并最终把这三个通道合并在一起。

2.2 获取 YUV 视频

为了方便演示,我们使用 YUV420P 的视频,即4个Y共用一个U,V 分量,且存储是先存储Y,然后是U,最后再存储V分量。
这里我们可以用 ffmepg 的命令,轻松把一个 MP4 的视频转换成 YUV,由于 YUV 比较大,记得修改分辨率,这样小一些:

ffmpeg -i input.mp4 -s 288x512 -r 30 -pix_fmt yuv420p out.yuv

2.3 读取 yuv 文件数据

之后,就可以通过不断读取这个yuv文件,拿到y,u,v的数据,假设视频大小为 wxh ,则先读取 wh 个y,再读取 wh/4 个u,再读取 w*h/4 个 v;一帧读取完后,就进行渲染,然后再重复操作,直到文件被读取完毕。
我们把文件放在 assert 文件夹下:

 /**
  * 读取yuv数据,注意 w,h 为视频宽高
  */
 private fun readYuvData(w: Int, h: Int) {
     val input = context.resources.assets.open(YUV_FILE)
     //视频时 yuv420p ,4 个 y 共用一个 uv,先存储y,再u,和v
     val y = ByteArray(w * h)
     val u = ByteArray(w * h / 4)
     val v = ByteArray(w * h / 4)

     while (true) {
         if (isExit) {
             Log.d(TAG, "readYuvData,手动退出")
             return
         }
         val readY = input.read(y)
         val readU = input.read(u)
         val readV = input.read(v)
         //都读到分量
         if (readY > 0 && readU > 0 && readV > 0) {
             //从这里触发刷新
             bufferY = ByteBuffer.wrap(y)
             bufferU = ByteBuffer.wrap(u)
             bufferV = ByteBuffer.wrap(v)

             val glView = view as GLSurfaceView
             //主动触发刷新
             glView.requestRender()
             //延时30ms,控制速度
             Thread.sleep(30)

         } else {
             Log.d(TAG, "readYuvData,文件末尾,退出")
             return
         }
     }
 }

2.4 着色器编写

顶点着色器,沿用上一章,不需要改变,但是我们把位置改一下,让它填充整个屏幕:

private val POINT_RECT_DATA2 = floatArrayOf(
    // positions           // texture coords
    1f,  1f, 0.0f, 1.0f, 0.0f, // top right
    1f, -1f, 0.0f, 1.0f, 1.0f, // bottom right
   -1f, -1f, 0.0f, 0.0f, 1.0f, // bottom left
   -1f,  1f, 0.0f, 0.0f, 0.0f  // top left
)

片段着色中,设置三个纹理,用来读取 yuv分量的数据:

private const val FRAGMENT_SHADER = """#version 300 es
    precision mediump float;
    out vec4 FragColor;
    in vec2 vTexture;
    uniform sampler2D textureY;
    uniform sampler2D textureU;
    uniform sampler2D textureV;
    void main() {
        //采样到的yuv向量数据  
         float y,u,v;
        //yuv转化得到的rgb向量数据
        vec3 rgb;
        //分别取yuv各个分量的采样纹理
        y = texture(textureY, vTexture).r;
        u = texture(textureU, vTexture).g - 0.5;
        v = texture(textureV, vTexture).b - 0.5;
        //yuv转化为rgb, https://en.wikipedia.org/wiki/YUV
        rgb.r = y + 1.540*v;
        rgb.g = y - 0.183*u - 0.459*v;
        rgb.b = y + 1.818*u;
        FragColor = vec4(rgb, 1.0);
    
    }
"""

可以看到,我们使用了三个纹理textureY,textureU,textureV,然后用了三个变量 y,u,v 用来接收纹理数据。
前面说到,OpenGL 的分量,除了包含位置信息{x,y,z,w},还有颜色(r,g,b,a)和纹理信息(s,t,r,q):

  • x,y,z,w: 与位置相关的分量
  • r,g,b,a: 与颜色相关的分量
  • s,t,p,q: 与纹理坐标相关的分量

当我们设置 sampler2D 的类型为 GL_LUMINANCE,所以 texture().r 拿到的是yuv 的第一个颜色向量的第一个分量信息,就是y;

那这个 0.5 是什么?为啥要减去它?
先看到YUV与RGB 的转换公司,这里用高清模式(BT709),颜色空间为 Limited Range 的转换公式:(图片来源)
在这里插入图片描述
可以看到,有个转换偏差值,而 U,V 默认是127 ,Y 的偏移量为0。8 个 bit 位的取值范围是 0 ~ 255,由于在 shader 中纹理采样值需要进行归一化(注意,纹理的范围是[0,1]),所以 UV 分量的采样值需要分别减去 0.5 ,确保 YUV 到 RGB 正确转换。

2.5 纹理加载

编写完着色器,就可以编写纹理对象了。首先,设置纹理的下标:

private val textures = IntArray(3)
 //三个纹理,需要设置纹理的下标
GLES30.glUniform1i(GLES30.glGetUniformLocation(programId, "textureY"), 0)
GLES30.glUniform1i(GLES30.glGetUniformLocation(programId, "textureU"), 1)
GLES30.glUniform1i(GLES30.glGetUniformLocation(programId, "textureV"), 2)

设置纹理的对象:


GLES30.glGenTextures(3, textures, 0)
for (i in 0..2) {
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[i])

    //纹理环绕
    GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_REPEAT)
    GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_REPEAT)

    //纹理过滤
    GLES30.glTexParameteri(
        GLES30.GL_TEXTURE_2D,
        GLES30.GL_TEXTURE_MIN_FILTER,
        GLES30.GL_NEAREST
    )
    GLES30.glTexParameteri(
        GLES30.GL_TEXTURE_2D,
        GLES30.GL_TEXTURE_MAG_FILTER,
        GLES30.GL_LINEAR
    )

    //解绑纹理对象
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
}

前面2.3章节,已经拿到了 yuv 的数据,这里,我们使用 glTexImage2D 把数据设置给纹理:

    override fun onDrawFrame(gl: GL10?) {
        //步骤1:使用glClearColor设置的颜色,刷新Surface
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

        //使用 y 数据
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0])
        GLES30.glTexImage2D(
            GLES30.GL_TEXTURE_2D,
            0,
            GLES30.GL_LUMINANCE,
            w,
            h,
            0,
            GLES30.GL_LUMINANCE,
            GLES30.GL_UNSIGNED_BYTE,
            bufferY
            )
        //使用 u 数据
        GLES30.glActiveTexture(GLES30.GL_TEXTURE1)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[1])
        GLES30.glTexImage2D(
            GLES30.GL_TEXTURE_2D,
            0,
            GLES30.GL_LUMINANCE,
            w / 2,
            h / 2,
            0,
            GLES30.GL_LUMINANCE,
            GLES30.GL_UNSIGNED_BYTE,
            bufferU
        )
        //使用 v 数据
        GLES30.glActiveTexture(GLES30.GL_TEXTURE2)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[2])
        GLES30.glTexImage2D(
            GLES30.GL_TEXTURE_2D,
            0,
            GLES30.GL_LUMINANCE,
            w / 2,
            h / 2,
            0,
            GLES30.GL_LUMINANCE,
            GLES30.GL_UNSIGNED_BYTE,
            bufferV
        )
        GLES30.glBindVertexArray(vao[0])
        GLES30.glDrawElements(GLES30.GL_TRIANGLE_STRIP, 6, GLES30.GL_UNSIGNED_INT, 0)
        bufferY?.clear()
        bufferU?.clear()
        bufferV?.clear()
    }

效果如下:
在这里插入图片描述

三.加抖音特效

从纹理那张可知
Android OpenGL ES 学习(七) – 纹理
我们可以通过:

FragColor = mix(texture1,texture2,0.5);

的方式去添加纹理的效果。因此,我们也可以修改片段着色器中的 RGB 数据,实现不同的效果。

3.1 灰度

比如灰度,只需要修改rgb的颜色,即可,你可以把 u,v 分量去掉:

// u = texture(textureU, vTexture).g - 0.5;
// v = texture(textureV, vTexture).b - 0.5;
 u = 0.0;
 v = 0.0;

也可以使用算法:

float gray = rgb.r * 0.2126 + rgb.g * 0.7152 + rgb.b * 0.0722;
FragColor = vec4(gray,gray,gray, 1.0);

得到灰度视频:
在这里插入图片描述

3.2 颜色反转

让每个颜色值反转,我们只需要1 - 颜色值即可:

rgb.r = 1.0 - (y + 1.540*v);
rgb.g = 1.0 - (y - 0.183*u - 0.459*v);
rgb.b = 1.0 - (y + 1.818*u);
FragColor = vec4(rgb, 1.0);

在这里插入图片描述

3.3 对称不同颜色值

什么意思呢?就是让左上角颜色反转,右下角灰色,其他区域正常显示,如下视频:
在这里插入图片描述

思路是对y进行分割,取中间(0.5,0.5)作为分割点:

if(vTexture.x <= 0.5 && vTexture.y <= 0.5){
    //左上角,使用反色
    float r = 1.0 - rgb.r;
    float g = 1.0 - rgb.g;
    float b = 1.0 - rgb.b;
    FragColor = vec4(r,g,b, 1.0);
}else if(vTexture.x > 0.5 && vTexture.y > 0.5){
   
     //右下角,使用灰度
    float gray = rgb.r * 0.2126 + rgb.g * 0.7152 + rgb.b * 0.0722;
    FragColor = vec4(gray,gray,gray, 1.0);
}else{
    FragColor = vec4(rgb, 1.0);
}

3.4 二/三分屏

分屏这个原理呢,需要抽象一下,拿二分屏来说,其实最终操作的是纹理坐标的值。
如下图:
在这里插入图片描述
比如,因为要二分屏,实际上显示的,肯定不是全部内容,如显示区域为 0.25 到0.75 范围,实际就是把这个范围,填充到上下两个区域,分割线为0.5.

这样,上半部分(0,0)到(0,0.5) 要显示时,实际是从(0,0.25),(0,0.75)的内容,同理下半部分,也是(0,0.5)到(0,1.0),实际也是 (0,0.25),(0,0.75)。
因此,我们修改 y 分量的大小即可:

//输入是不能被修改的,所以使用一个vec2 分量
vec2 uv = vTexture.xy;
if(uv.y >= 0.0 && uv.y <= 0.5){
    uv.y = uv.y + 0.25;
}else{
    uv.y = uv.y - 0.25;
}

//分别取yuv各个分量的采样纹理
y = texture(textureY, uv).r;
u = texture(textureU, uv).g - 0.5;
v = texture(textureV, uv).b - 0.5;

就可以得到二分屏:
在这里插入图片描述
同理,我们可以得到三分屏的效果:

if(uv.y >= 0.0 && uv.y <= 0.2){
    uv.y = uv.y + 0.3;
}else if(uv.y > 0.8){
    uv.y = uv.y - 0.5;
}

在这里插入图片描述
参考:
https://juejin.cn/post/7160304816877469733
https://juejin.cn/post/7168042219163779108
https://time.geekbang.org/column/article/449795
https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/05%20Framebuffers/

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

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

相关文章

基于MWORKS.Sysplorer的电子控制器应用案例——永磁同步电机FOC算法建模

1 前言 MWORKS是面向数字工程的新一代科学计算与系统建模仿真平台&#xff0c;可提供机械、电子、液压、控制、热、信息等多领域统一建模仿真环境。经过同元持续攻关&#xff0c;全新推出的MWORKS.Sysplorer嵌入式代码生成器&#xff0c;现已支持面向电子控制器的产品级的嵌入…

循环神经网络的简洁实现

参考8.6. 循环神经网络的简洁实现 — 动手学深度学习 2.0.0 documentation 本节将展示如何使用深度学习框架的高级API提供的函数更有效地实现相同的语言模型。 我们仍然从读取时光机器数据集开始。 pip install mxnet1.7.0.post1 pip install d2l0.15.0 from mxnet import n…

ubuntu18.04下用Fiddler抓取curl库网络数据包总结

本人在ubuntu18.04下进行开发&#xff0c;需要使用http和服务端进行通信&#xff0c;为了确认自己发送给服务端和服务端返回数据字段&#xff0c;所以需要进行抓包分析参数。本文就说明一下如何在ubuntu18.04使用fidder对自己编写的应用程序进行http协议数据包抓取。 目录 1.…

无线网络渗透测试清单

©网络研究院 无线渗透测试积极检查 WiFi 网络中的信息安全措施的过程&#xff0c;并分析弱点、技术流程和关键无线漏洞。 我们应该关注的最重要的对策是威胁评估、数据盗窃检测、安全控制审计、风险预防和检测、信息系统管理和升级基础设施&#xff0c;并且应该准备一份…

13-14-15-RabbitMq工作模式深度剖析与Spring整合MQ以及RabbitMq高级特性

RabbitMQ消息传递流程 连接( Connection) 在RabbitMQ中&#xff0c;生产者和消费者与RabbitMQ的通信就是基于TCP连接的。不过呢我们知道TCP连接的创建和销毁在高并发场景下对于操作系统来说都是特别昂贵的开销&#xff0c;所以RabbitMQ又引入了信道的概念 信道&#xff08;Chan…

云原生之使用Docker部署轻量级web服务器lighthttpd

云原生之使用Docker部署轻量级web服务器lighthttpd一、Lighthttpd介绍二、检查系统版本三、检查docker状态四、下载lighthttpd镜像五、部署lighthttpd1.创建数据目录2.创建lighthttpd容器3.查看容器状态六、访问lighthttpd服务七、编辑index.html1.编辑index.html文件2.重新访问…

Hadoop大数据存算分离方案:计算层无缝对接存储系统

Hadoop的诞生改变了企业对数据的存储、处理和分析的过程&#xff0c;加速了大数据的发展。随着大数据系统建设的深入&#xff0c;企业的数据基础设施易出现计算资源浪费、存储性能低、管理成本过高等挑战。相比存算一体架构&#xff0c;存算分离架构具有性能与成本最优、兼具灵…

3D地图app

3D三维地图APP 发布时间&#xff1a;2018-07-19 版权&#xff1a; 3D地图依据高程数据等对地表进行渲染&#xff0c;实现地表的起伏&#xff0c;模拟出真实的三维场景&#xff0c;让你有如身临其境般的感觉。 &#xff08;注&#xff1a;Bigemap 3D地图是一个三维地图浏览功能…

项目沟通怎么才能不像在吵架?

项目沟通并非吵架&#xff0c;看起来却总是剑拔弩张。有效沟通才能真正解决问题&#xff0c;笔者给出了一些实用的建议&#xff0c;从对象到场景&#xff0c;再到方法与技巧&#xff0c;应该在沟通中有针对性地注意这些问题。 沟通是个老话题&#xff0c;在项目管理中有专门讲沟…

draw.io使用教程

大部分的绘图应用都离不开三个基本的元素&#xff0c;图形&#xff0c;链接&#xff0c;文本。每个元素都有基本的操作和样式&#xff0c;元素与元素之间又可以进行组合&#xff0c;“三生万物”&#xff0c;生成各种各样的图表。 如果没有这款绘图的 可以点击获取 : drawio文…

企业项目管理的不同与好处

大型企业组织通常同时运行多个复杂项目。尽管这些项目看起来不一定相互关联&#xff0c;但它们都会影响同一个企业组织。企业项目管理(EPM)是指在公司范围内管理项目的实践。它通常涉及实施战略和流程&#xff0c;以大规模简化和提高项目管理的有效性。根据项目管理协会(PMI)的…

burpsuite靶场——XXE

文章目录什么是XML&#xff1f;什么是XML实体&#xff1f;什么是文档类型定义(DTD)&#xff1f;什么是XML自定义实体&#xff1f;什么是XML外部实体&#xff1f;使用外部实体利用 XXE 来检索文件利用 XXE 执行 SSRF 攻击盲XXE漏洞带外交互的盲 XXE过 XML 参数实体进行带外交互的…

【AJAX】AJAX的跨域问题

AJAX的跨域问题跨域的概述区别同源与不同源同源策略有什么用&#xff1f;AJAX跨域解决方案方案一、设置响应头方案二、jsonp方案三、代理机制&#xff08;httpclient&#xff09;跨域的概述 跨域是指从一个域名的网页去请求另一个域名的资源。比如从百度&#xff08;https://ba…

WPF控件模板、数据模板、容器样式选择器

WPF控件模板 利用Tag来绑定控件模板内容 <!--模板定义--> <Style x:Key"ButtonStyle1" TargetType"{x:Type Button}"><Setter Property"Template"><Setter.Value><ControlTemplate TargetType"{x:Type Button…

声音事件检测metric:PSDS

论文&#xff1b;A FRAMEWORK FOR THE ROBUST EVALUATION OF SOUND EVENT DETECTION Abstract 这项工作为多声道声音事件检测&#xff08;SED&#xff09;系统的性能评估定义了一个新的框架&#xff0c;它克服了传统的collar-based事件决定、事件F-cores和事件错误率的限制。…

【Kotlin 协程】Flow 流组合 ( Flow#zip 组合多个流 | 新组合流的元素收集间隔与被组合流元素发射间隔的联系 )

文章目录一、Flow 流组合1、Flow#zip 组合多个流2、新组合流的元素收集间隔与被组合流元素发射间隔的联系一、Flow 流组合 1、Flow#zip 组合多个流 调用 Flow#zip 函数 , 可以将两个 Flow 流合并为一个流 ; Flow#zip 函数原型 : /*** 将来自当前流( this )的值压缩到[其他]流&…

第二十六章 数论——欧拉函数(详解与证明)

第二十六章 数论——欧拉函数&#xff08;详解与证明&#xff09;欧拉函数1、互质2、欧拉函数的定义3、欧拉函数的公式4、欧拉函数的证明5、欧拉函数的使用&#xff08;1&#xff09;问题一&#xff1a;思路代码&#xff08;2&#xff09;问题二&#xff1a;思路case1case1case…

2022/12/17 MySQL索引失效的底层原理

1 复合索引-最左前缀原理 where子句中使用最频繁的一列放在最左边&#xff1b;我们在&#xff08;a,b,c&#xff09;字段上建了一个联合索引&#xff0c;所以这个索引是先按a 再按b 再按c进行排列的&#xff0c;所以&#xff1a;以下的查询方式都可以用到索引 select * from …

emacs下安装eaf

emacs下安装eaf插件 原因 eaf插件一开始还有点排斥&#xff0c;觉得emacs终端下操作多好多流畅。想要浏览器&#xff0c;终端和pdf再快速切换就可以了&#xff0c;毕竟我用i3wm/yabai窗口管理器。 但是想到当初也是vim用的多学得多&#xff0c;emacs就不愿意去接触学习&#…

Linux系统下的压缩和解压指令

Linux系统下的压缩和解压指令 gzip/gunzip指令 gzip&#xff1a;用于压缩文件&#xff1b;gunzip&#xff1a;用于解压的 语法&#xff1a;gzip file 以及 gunzip file.gz (压缩文件&#xff0c;只能将文件压缩为*.gz文件) gzip /home/hello.txt: gzip压缩&#xff0c;将/home下…