一文详解如何将 ExternalOES转换为 TEXTURE_2D纹理

在使用OpenGL ES进行图形图像开发时,我们常使用GL_TEXTURE_2D纹理类型,它提供了对标准2D图像的处理能力。这种纹理类型适用于大多数场景,可以用于展示静态贴图、渲染2D图形和进行图像处理等操作。
另外,有时我们需要从Camera或外部视频源读取数据帧并进行处理。这时,我们会使用GL_TEXTURE_EXTERNAL_OES纹理类型。其专门用于对外部图像或实时视频流进行处理,可以直接从 BufferQueue 中接收的数据渲染纹理多边形,从而提供更高效的视频处理和渲染性能。

在实际应用中,我们通常将GL_TEXTURE_2DGL_TEXTURE_EXTERNAL_OES这两种纹理类型分开使用,并且它们互不干扰。实际上,这种情况占据了80%的使用场景。我们可以根据具体需求选择合适的纹理类型进行处理和渲染。
然而,有时候我们也会遇到一些特殊情况,需要将GL_TEXTURE_EXTERNAL_OES纹理转化为GL_TEXTURE_2D纹理进行视频处理或计算。这种情况可能出现在需要对视频数据进行特殊的图像处理或者与GL_TEXTURE_2D纹理类型的其他渲染操作进行交互时。

当以上情况出现时,我们该如何处理呢?难道是直接将GL_TEXTURE_EXTERNAL_OES纹理赋值给GL_TEXTURE_2D纹理使用(经过实验这种方式是不可用的)?
这里对此情况,先给出解决方案,一般我们可以通过一些技术手段,如离屏渲染或FrameBuffer帧缓冲区对象,将GL_TEXTURE_EXTERNAL_OES纹理转换为GL_TEXTURE_2D纹理,并进行后续的处理和计算。
而此篇文章主要记录,我是如何通过FrameBuffer帧缓冲区对象,将GL_TEXTURE_EXTERNAL_OES纹理数据转化为GL_TEXTURE_2D纹理数据的!

  • 首先 回顾一下GL_TEXTURE_2D纹理与GL_TEXTURE_EXTERNAL_OES纹理;
  • GL_TEXTURE_EXTERNAL_OES纹理数据通过FrameBuffer转化为GL_TEXTURE_2D纹理数据

一、TEXTURE_2DEXTERNAL_OES

在正式研究 “GL_TEXTURE_EXTERNAL_OES纹理数据转化为GL_TEXTURE_2D纹理数据” 之前,先要搞清楚:

  • 什么是GL_TEXTURE_2D纹理?
  • 什么又是GL_TEXTURE_EXTERNAL_OES纹理?
  • GL_TEXTURE_2D纹理与GL_TEXTURE_EXTERNAL_OES纹理有什么样的区别?

1.1 GL_TEXTURE_2D纹理

GL_TEXTURE_2D 提供了对标准2D图像的处理能力,可以存储静态的贴图图像或者帧缓冲区的渲染结果
其使用二维的纹理坐标系,通过将纹理坐标映射到纹理图像上的对应位置,可以实现纹理贴图、纹理过滤、纹理环绕等操作

GL_TEXTURE_2D纹理的特点包括:

  • 使用二维纹理坐标系进行操作;
  • 使用glTexImage2D函数加载纹理数据;
  • 通过纹理过滤和纹理环绕等方式进行纹理的采样和处理;

GL_TEXTURE_2D纹理:创建、绑定、采样、加载纹理图像

public static int createDrawableTexture2D(Context context, int drawableId) {
    // 生成纹理ID
    int[] textures = new int[1];
    GLES30.glGenTextures(1, textures, 0);
    // 绑定纹理
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
    // 纹理采样方式
    GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
    GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
    GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
    GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
    // texImage2D加载图像数据
    InputStream is = context.getResources().openRawResource(drawableId);
    Bitmap bitmapTmp;
    try {
        bitmapTmp = BitmapFactory.decodeStream(is);
    } finally {
        try {
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    GLUtils.texImage2D(GLES30.GL_TEXTURE_2D,0, GLUtils.getInternalFormat(bitmapTmp), bitmapTmp, GLUtils.getType(bitmapTmp), 0 );
    bitmapTmp.recycle();
    return textures[0];
}

GL_TEXTURE_2D纹理:Shader处理阶段(片元着色器)

precision mediump float;  
varying vec2 v_texture_coord;  
uniform sampler2D MAIN;  
void main() {  
   vec4 color=texture2D(MAIN, v_texture_coord);  
   gl_FragColor=color;  
}

GL_TEXTURE_2D纹理:纹理渲染

GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texId);
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);

1.2 GL_TEXTURE_EXTERNAL_OES纹理类型

根据AOSP: SurfaceTexture 文档描述,GL_TEXTURE_EXTERNAL_OES 是一种特殊的纹理类型,主要用于处理外部图像或视频数据,如从摄像头捕捉的实时图像和外部视频流。
GL_TEXTURE_EXTERNAL_OES 相对于 GL_TEXTURE_2D 最大的特点就是 GL_TEXTURE_EXTERNAL_OES可直接从 BufferQueue 中接收的数据渲染纹理多边形

GL_TEXTURE_EXTERNAL_OES纹理类型的特点包括:

  • 需采用特殊的采样器类型纹理着色器扩展
  • 使用二维纹理坐标系进行操作,与GL_TEXTURE_2D相似。
  • 专门用于处理外部图像或视频数据,可直接从 BufferQueue 中接收的数据渲染纹理多边形,从而提供更高效的视频处理和渲染性能。

对于此,官方文档中提供了一个 Grafika 的连续拍摄案例工程,并给出了如下参考流程图。
Google官方Grafika案例流程

通过阅读 Grafika 的连续拍摄案例,我们得知:

  • 首先,需创建一个OES纹理ID,用于接收Camera图像数据
// GL_TEXTURE_EXTERNAL_OES: 纹理创建、绑定、采样
public static int createTextureOES() {
    // 创建OES纹理ID
    int[] textures = new int[1];
    GLES30.glGenTextures(1, textures, 0);
    TextureUtil.checkGlError("glGenTextures");
    // 绑定OES纹理ID
    int texId = textures[0];
    GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
    TextureUtil.checkGlError("glBindTexture " + texId);
    // OES纹理采样
    GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
    GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
    GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
    GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
    TextureUtil.checkGlError("glTexParameter");
    return texId;
}
  • 完成OES纹理ID创建后,通过oesTextureId创建一个图像消费者SurfaceTexture,将SurfaceTexture设定为预览的PreviewTexture;
// 传入一个OES纹理ID
SurfaceTexture mSurfaceTexture = new SurfaceTexture(oesTextureId);  
// 将 SurfaceTexture 设置为预览的 PreviewTexture
Camera.setPreviewTexture(mSurfaceTexture);
  • 或者通过SurfaceTexture创建Surface,将Surface对象传递给MediaPlayerMediaCodec进行视频帧数据获取;
// 传入一个OES纹理ID
SurfaceTexture mSurfaceTexture = new SurfaceTexture(oesTextureId);  
// 创建 Surface 
Surface mSurface = new Surface(mSurfaceTexture);
// 将 Surface 设置给 MediaPlayer 外部视频播放器,获取视频帧数据
MediaPlayer.setSurface(surface);
  • 前文已经提到GL_TEXTURE_EXTERNAL_OES纹理类型 可直接从Surface对应的BufferQueue中获取视频流数据;
  • 在获取到视频帧数据后:
    一方面,可通过OpenGL的渲染管线,将GL_TEXTURE_EXTERNAL_OES纹理渲染到GLSurfaceView上,完成图像数据的预览
    另一方面,可将GL_TEXTURE_EXTERNAL_OES纹理,通过离屏渲染的形式,写入到 MediaCodeC,硬编码生成MP4视频。
// GL_TEXTURE_EXTERNAL_OES纹理:Shader处理阶段(片元着色器)
#extension GL_OES_EGL_image_external : require  
precision mediump float;  
varying vec2 v_texture_coord;  
uniform samplerExternalOES MAIN;  
void main() {  
   vec4 color=texture2D(MAIN, v_texture_coord);  
   gl_FragColor=color;  
}

GL_TEXTURE_EXTERNAL_OES纹理:纹理渲染

// 纹理渲染阶段:GL_TEXTURE_EXTERNAL_OES纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_EXTERNAL_OES, texId);
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);

1.3 关于两者的区别 我的个人理解

关于两者的区别,我的个人理解:
GL_TEXTURE_2D纹理类型与GL_TEXTURE_EXTERNAL_OES纹理类型,在数据来源纹理数据的存储格式上存在差异。

  • 数据来源方面
    一个来源于glTexImage2D加载的二维图像数据
    一个来源与图像消费者Surface对应的BufferQueue
  • 纹理存储格式
    GL_TEXTURE_EXTERNAL_OES数据来源于外部视频源或Camera,其数据格式可能为YUV或RGB;
    GL_TEXTURE_2D的数据格式则依赖于开发中setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)的配置,可能是RGBA8888,也可能是RGBA4444等等。

由于两者数据来源和纹理存储格式的差异,两种纹理类型是不能直接进行转化的。

  • 首先,其在纹理采样阶段Shader处理阶段纹理渲染阶段均不同程度的存在差异(这一点在上一节的两者对比的代码举例中可以证明)。
  • 其次,如果需要在处理和计算阶段将GL_TEXTURE_EXTERNAL_OES纹理转换GL_TEXTURE_2D纹理,通常需要使用离屏渲染或帧缓冲区对象等技术手段。

二、EXTERNAL_OES转化为TEXTURE_2D纹理数据

这里直接介绍转化过程

OES纹理数据转化TEXTURE2D纹理数据

  • 首先,需创建一个OES纹理ID(相关代码举例在前文已经给出);
  • 完成OES纹理ID创建后,通过oesTextureId创建一个图像消费者SurfaceTexture(相关代码举例在前文已经给出);
  • 通过SurfaceTexture创建Surface,将Surface对象传递给MediaPlayer,获取Sdcard中对应路径的视频帧数据获取(相关代码举例在前文已经给出);
  • 创建FRAMEBUFFER帧缓冲区,并绑定GL_TEXTURE_2D空白纹理对象;
public static int createEmptyTexture2DBindFrameBuffer(int[] frameBuffer, int texPixWidth, int texPixHeight) {
    // 创建纹理ID
    int[] textures = new int[1];
    GLES30.glGenTextures(1, textures, 0);
    // 绑定GL_TEXTURE_2D纹理
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
    // 纹理采样
    GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
    GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
    GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
    GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
    // 创建一个空的2D纹理对象,指定其基本参数,并绑定到对应的纹理ID上
    GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, texPixWidth, texPixHeight,0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
    // 取消绑定纹理
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, GLES30.GL_NONE);

    /**
     * 帧缓冲区
     */
    // 创建帧缓冲区
    GLES30.glGenFramebuffers(1, frameBuffer, 0);
    // 将帧缓冲对象绑定到OpenGL ES上下文的帧缓冲目标上
    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]);
    // 使用GLES30.GL_COLOR_ATTACHMENT0将纹理作为颜色附着点附加到帧缓冲对象上
    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textures[0], 0);
    // 取消绑定缓冲区
    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);

    return textures[0];
}
  • GL_TEXTURE_EXTERNAL_OES纹理渲染FRAMEBUFFER帧缓冲区中;
// 激活纹理
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
// 将所需的纹理对象绑定到Shader中纹理单元0上
GLES30.glUniform1i(mOesTextureIdHandle, 0);
// 绑定纹理
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
// 绑定FRAMEBUFFER缓冲区
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, tex2DFrameBufferId);
// 绘制矩形
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
// 取消FRAMEBUFFER的绑定
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
  • 最后,绘制渲染GL_TEXTURE_2D纹理,完成纹理图像的显示。
// 激活纹理
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
// 将所需的纹理对象绑定到Shader中纹理单元0上
GLES30.glUniform1i(mTex2DIdHandle, 0);
// 绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, tex2DId);
// 绘制矩形
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);

三、源码下载

ExternalOES纹理数据 转换为 TEXTURE-2D纹理数据:
https://download.csdn.net/download/aiwusheng/88650498

工程代码截图

参考

AOSP:SurfaceTexture
https://source.android.google.cn/docs/core/graphics/arch-st?hl=zh-c

Github:Google Grafika
https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java

OpenGL渲染管线:
https://xiaxl.blog.csdn.net/article/details/121467207

纹理ID 离屏渲染 写入到Surface中:
https://xiaxl.blog.csdn.net/article/details/131682521

MediaCodeC与OpenGL硬编码录制mp4:
https://xiaxl.blog.csdn.net/article/details/72530314

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用docker创建自己的Android编译容器

文章目录 背景步骤1.创建Dockerfile2.编写Dockerfile指令3.编译4.使用 背景 每次拿到新机器或者系统重装,最麻烦的就是各种环境配置,最近学习了一下docker的知识,用dockerfile创建一个Android编译容器,这样就不用每次都吭哧吭哧的…

OpenCV如何以指定分辨率打开摄像头(C++ / Python代码演示)

问题背景 使用OpenCV打开USB摄像头时经常会遇到一个问题:我的摄像头最高分辨率是1920 * 1080,为什么用OpenCV打开摄像头保存的图片每次都是640 *480?能不能以最高分辨率打开并保存图片呢? 如何解决 首先需要确认自己的摄像头支持的最大分辨率是多少,具体步骤可以参考下…

处理HTTP错误和异常在Go语言中的最佳实践

在Go语言中,处理HTTP错误和异常是非常重要的。下面是一些最佳实践,帮助您有效地处理HTTP错误和异常。 定义错误类型 首先,定义一个自定义的错误类型,以便在处理HTTP错误时提供更清晰的错误信息。您可以使用标准库中的error类型作…

【Xcode】解决Unable to process request - PLA Update available

出现场景 IOS更新app时,使用Xcode上传新版本的包时,提示无法上传。 Unable to process request -PLA update available you currently dont have access to this membership resource. To resolve this issue ,agree to the latest program license a…

PHP-Xlswriter高性能导出Excel

使用背景 使用传统的PHPExcel导出效率太慢,并且资源占用高,数据量大的情况,会导致服务占用大量的资源,从而导致生产意味,再三思索后,决定使用其他高效率的导出方式 PHP-Xlswriter PHPExcel 因为内存消耗过…

福建农林大学 html +css + JavaScript 期末复习 -- 保姆级

html css JavaScript 期末复习&#xff08;保姆级复盘&#xff09; 考试题型 1、选择题 20题 30分 2、判断题 15题 15分 3、程序题 3 题 30分 4、综合题 2 题 25分 1、网页第一代文本标签&#xff08;直接上代码&#xff0c;看保姆级注解&#xff09; <!-- doctype: docum…

文件上传——后端

文件上传流程&#xff1a; 创建阿里云OSS&#xff08;对象存储服务&#xff09;的bucket 登录阿里云&#xff0c;并完成实名认证&#xff0c;地址&#xff1a;https://www.aliyun.com/. 可以通过搜索&#xff0c;进入以下页面&#xff1a; 点击立即使用后&#xff1a; 点击…

hex和rgb色值转换-色彩加深减淡

我们在做主题订制的时候&#xff0c;一般都会选一种主题色&#xff0c;该颜色以主题色为主导&#xff0c;颜色依次变浅&#xff0c;用于做主题色下的关联色统一&#xff0c;例如文字激活、激活的背景色、菜单背景色等 在项目中主题色的应用&#xff1a; 如果你在项目中允许用…

create-react-app 打包去掉 map文件

前言&#xff1a; 在使用 create-react-app 创建的React应用中&#xff0c;默认情况下会生成带有.map文件的打包文件&#xff0c;这些.map文件包含了源代码和调试信息&#xff0c;用于开发和调试过程中进行错误跟踪。然而&#xff0c;在生产环境中&#xff0c;这些.map文件通常…

ISA95 及工业互联网平台

ISA95简称S95&#xff0c;是美国仪表、系统和自动化协会&#xff08;ISA&#xff09;在95年提出来的&#xff0c;也是这个协会启动编制的第95个标准项目。它定义了企业商业和控制系统之间的集成&#xff0c;主要可以分成三个层次&#xff1a; 第0&#xff0c;1&#xff0c;2层…

Ubuntu20.04 及深度学习环境anaconda、cuda、cudnn、pytorch、paddle2.3安装记录

学习目标&#xff1a; Ubuntu20.04下装好torch、paddle深度学习环境。 选择的版本环境是 &#xff1a;最新的nvidia驱动、cuda 11.1 、cudnn v8.1.1&#xff0c;下面会说为啥这么选。 学习内容&#xff1a; 1. Ubuntu20.04仓库换源 本节参考Ubuntu 20.04 Linux更换源教程 2…

构建搜索引擎,而非向量数据库(Vector DB) [译]

原文&#xff1a;Build a search engine, not a vector DB 作者&#xff1a; Panda Smith 在过去 12 个月中&#xff0c;我们见证了向量数据库&#xff08;Vector DB&#xff09;创业公司的迅猛增长。我此刻并不打算深入探讨它们各自的设计取舍。相反&#xff0c;我更想探讨和…

2018年第七届数学建模国际赛小美赛C题共享单车对城市交通的影响解题全过程文档及程序

2018年第七届数学建模国际赛小美赛 C题 共享单车对城市交通的影响 原题再现&#xff1a; 共享自行车改变了许多城市的交通状况&#xff0c;许多大城市引入共享自行车来解决交通问题。我们需要定量评估共享自行车对城市交通的影响&#xff0c;以及相关的经济、社会和环境影响。…

苹果如何从iCloud恢复备份?正确方法看这里!

iCloud为所有苹果用户免费提供5G内存空间&#xff0c;用户可以将照片、短信、联系人、备忘录等重要信息备份到iCloud云端&#xff0c;这样可以方便在不同设备之间同步和共享。 同时&#xff0c;iCloud保证这些数据在所有苹果设备上及时自动更新。当遇到手机数据丢失时&#xf…

光纤的连接来了

光纤在工程布线中&#xff0c;难免会遇到线不够长或者磨损折断的情况&#xff0c;要怎么处理呢&#xff1f; 首先看看光纤的结构&#xff1a; 纤芯&#xff1a;中心部分&#xff0c;光波在纤芯中传输。 包层&#xff1a;环绕纤芯&#xff0c;折射率低于纤芯&#xff0c;作用是…

工业自动化的通信核心—钡铼技术R10A工业级路由器介绍

随着工业自动化的快速发展&#xff0c;工业通信技术也日新月异。在这个信息时代&#xff0c;工业通信设备的稳定性、可靠性和高效性变得尤为重要。作为工业自动化的核心部件之一&#xff0c;钡铼技术R10A工业级路由器以其出色的性能和卓越的功能在行业内赢得了广泛的赞誉。本文…

关于外贸包裹的那些事

大早晨收到一个客户留言&#xff0c;询问能不能看一下他的货物包裹被送到了哪里&#xff0c;然后客户可以安排他的代理人联系去取包裹&#xff0c;我心里的第一感觉是难道包裹丢失了&#xff1f; 于是赶紧起来查看物流单号&#xff0c;单号显示早在半个多月前已经被他的国内代…

C/C++ 块作用域的静态变量static的应用

块作用域的静态变量 静态变量(static variable)听起来自相矛盾&#xff0c;像是一个不可变的变量。实际上&#xff0c;静态的意思是该变量在内存中原地不动&#xff0c;并不是说它的值不变。具有文件作用域的变量自动具有&#xff08;也必须是&#xff09;静态存储器。创建的具…

Python实现接口测试总结--PyMySql库+封装

import pymysql # 封装数据库工具类 class DBUtil ( object ): # 添加类属性 conn None classmethod def __get_conn ( cls ): # 判断 conn 是否为空&#xff0c;如果是&#xff0c;创建 if cls . conn is None : cls . conn pymysql . connect ( host…

前端微信小程序AES加密解密踩坑

项目场景&#xff1a; 今天蛮沮丧的&#xff0c;在和别人对接的时候aes加解密的时候踩了坑。今天有个同事请假了&#xff0c;所以本来他和别人对接的活&#xff0c;老大给了我&#xff0c;然后我就正式踏上了战战兢兢的对接之路。 1.一开始的时候对面先是问用的啥加密方法。这…
最新文章