当前位置: 首页 > 编程日记 > 正文

用OpenGLES实现yuv420p视频播放界面

背景

例子TFLive这个项目里,是我按着ijkPlayer写的直播播放器,要运行需要编译ffmpeg的库,网盘里存了一份, 提取码:vjce。OpenGL ES播放相关的在在OpenGLES的文件夹里。

learnOpenGL学到会使用纹理就可以了。

播放视频,就是把画面一副一副的显示,跟帧动画那样。在解码视频帧数据之后得到的就是某种格式的一段内存,这段数据构成了一副画面所需的颜色信息,比如yuv420p。图文详解YUV420数据格式这篇写的很好。

YUV和RGB这些都叫颜色空间,我的理解便是:它们是一种约定好的颜色值的排列方式。比如RGB,便是红绿蓝三种颜色分量依次排列,一般每个颜色分量就占一个字节,值为0-255。

YUV420p, 是YUV三个分量分别三层,就像:YYYYUUVV。就是Y全部在一起,而RGB是RGBRGBRGB这样混合的。每个分量各自在一起的就是有平面(Plane)的。而420样式是4个Y分量和一对UV分量组合,节省空间。

要显示YUV420p的图像,需要转化yuv到rgba,因为OpenGL输出只认rgba。

iOS上准备工作

OpenGL部分在各平台逻辑是一致的,不在iOS上的可以跳过这段。

使用frameBuffer来显示:

  • 新建一个UIView子类,修改layer为CAEAGLLayer:
    +(Class)layerClass{return [CAEAGLLayer class];
    }
  • 开始绘制前构建Context:

    -(BOOL)setupOpenGLContext{_renderLayer = (CAEAGLLayer *)self.layer;_renderLayer.opaque = YES;_renderLayer.contentsScale = [UIScreen mainScreen].scale;_renderLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,nil];_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];//_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];if (!_context) {NSLog(@"alloc EAGLContext failed!");return false;}EAGLContext *preContex = [EAGLContext currentContext];if (![EAGLContext setCurrentContext:_context]) {NSLog(@"set current EAGLContext failed!");return false;}[self setupFrameBuffer];[EAGLContext setCurrentContext:preContex];return true;
    }
    • opaque设为YES是为了不做图层混合,去掉不必要的性能消耗。
    • contentsScale保持跟手机主屏幕一致,在不同手机上自适应。
    • kEAGLDrawablePropertyRetainedBacking为YES的时候会保存渲染之后数据不变,我们不需要这个,一帧视频数据显示完就没用了,所以这个功能关闭,去掉不必要的性能消耗。

有了这个context,并且把它设为CurrentContext,那么在绘制过程里的那些OpenGL代码才能在这个context生效,它才能把结果输出到需要的地方。

  • 构建frameBuffer,它是输出结果:

    -(void)setupFrameBuffer{glGenBuffers(1, &_frameBuffer);glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);glGenRenderbuffers(1, &_colorBuffer);glBindRenderbuffer(GL_RENDERBUFFER, _colorBuffer);[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_renderLayer];glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorBuffer);GLint width,height;glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);_bufferSize.width = width;_bufferSize.height = height;glViewport(0, 0, _bufferSize.width, _bufferSize.height);GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;if(status != GL_FRAMEBUFFER_COMPLETE) {NSLog(@"failed to make complete framebuffer object %x", status);}
    }
    • 建一个framebuffer
    • 建一个存储颜色的renderBuffer,但是它的内存是由contex来分配:[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_renderLayer];这一句比较关键。因为它,renderBuffer、context和layer才联系到了一起。根据Apple文档,负责显示的layer和renderbuffer是共用内存的,这样输出到renderBuffer里的内容,layer才显示。

OpenGL部分

分为两部分:第一次绘制开始前准备数据和每次绘制循环。

准备部分

使用OpenGL显示的逻辑是:画一个正方形,然后把输出的视频帧数据制作成纹理(texture)给这个正方形,把纹理显示处理就OK里。

所以绘制的图形是不变的,那么shader和数据(AVO等)都是固定的,在第一次开始前搞定后面就不需要变了。

    if (!_renderConfiged) {[self configRenderData];}
-(BOOL)configRenderData{if (_renderConfiged) {return true;}GLfloat vertices[] = {-1.0f, 1.0f, 0.0f, 0.0f, 0.0f,  //left top-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, //left bottom1.0f, 1.0f, 0.0f, 1.0f, 0.0f,   //right top1.0f, -1.0f, 0.0f, 1.0f, 1.0f,  //right bottom};//    NSString *vertexPath = [[NSBundle mainBundle] pathForResource:@"frameDisplay" ofType:@"vs"];
//    NSString *fragmentPath = [[NSBundle mainBundle] pathForResource:@"frameDisplay" ofType:@"fs"];//_frameProgram = new TFOPGLProgram(std::string([vertexPath UTF8String]), std::string([fragmentPath UTF8String]));_frameProgram = new TFOPGLProgram(TFVideoDisplay_common_vs, TFVideoDisplay_yuv420_fs);glGenVertexArrays(1, &VAO);glBindVertexArray(VAO);glGenBuffers(1, &VBO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GL_FLOAT), 0);glEnableVertexAttribArray(0);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GL_FLOAT), (void*)(3*(sizeof(GL_FLOAT))));glEnableVertexAttribArray(1);glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);//gen texturesglGenTextures(TFMAX_TEXTURE_COUNT, textures);for (int i = 0; i<TFMAX_TEXTURE_COUNT; i++) {glBindTexture(GL_TEXTURE_2D, textures[i]);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);}_renderConfiged = YES;return YES;
}
  • vertices 是正方形4个角的顶点坐标数据,每个点5个float数,前3个是xyz坐标,后两个是纹理坐标(uv)。xyz范围[-1, 1], uv范围[0, 1]。
  • 加载shader、编译,链接program,都在TFOPGLProgram这个类里做了。
  • 然后生成一个VAO和VBO绑定数据。
  • 最后构建几个纹理,虽然这时还没有数据,先占个位置。

绘制

先上shader:

const GLchar *TFVideoDisplay_common_vs ="               \n\
#version 300 es                                         \n\
                                                        \n\
layout (location = 0) in highp vec3 position;           \n\
layout (location = 1) in highp vec2 inTexcoord;         \n\
                                                        \n\
out highp vec2 texcoord;                                \n\
                                                        \n\
void main()                                             \n\
{                                                       \n\
gl_Position = vec4(position, 1.0);                      \n\
texcoord = inTexcoord;                                  \n\
}                                                       \n\
";
const GLchar *TFVideoDisplay_yuv420_fs ="               \n\
#version 300 es                                         \n\
precision highp float;                                  \n\\n\
in vec2 texcoord;                                       \n\
out vec4 FragColor;                                     \n\
uniform lowp sampler2D yPlaneTex;                       \n\
uniform lowp sampler2D uPlaneTex;                       \n\
uniform lowp sampler2D vPlaneTex;                       \n\\n\
void main()                                             \n\
{                                                       \n\// (1) y - 16 (2) rgb * 1.164                       \n\vec3 yuv;                                           \n\yuv.x = texture(yPlaneTex, texcoord).r;             \n\yuv.y = texture(uPlaneTex, texcoord).r - 0.5f;      \n\yuv.z = texture(vPlaneTex, texcoord).r - 0.5f;      \n\\n\mat3 trans = mat3(1, 1 ,1,                          \n\0, -0.34414, 1.772,               \n\1.402, -0.71414, 0                \n\);                                \n\\n\FragColor = vec4(trans*yuv, 1.0);                   \n\
}                                                       \n\
";
  • vertex shader就是输出一下gl_Position然后把纹理坐标传给fragment shader。

  • fragment shader是重点,因为要在这里完成从yuv到rgb的转换

  • 因为yuv420p是yuv3个分量分层存放的,如果将整个yuv数据作为整个纹理加载进来,那么用一个纹理坐标想取到3个分量,计算起来就比较麻烦了,每个fragment都需要计算。
    YyYYYYYY
    YYYYYYYY
    uUUUvVVV
    yuv420p的样子是这样的,加入你要取(2,1)这个坐标的颜色信息,那么y在(2,1),u在(1,3),v在(5,3)。而且高宽比例会影响布局:
    YyYYYYYY
    YYYYYYYY
    YyYYYYYY
    YYYYYYYY
    uUUUuUUU
    vVVVvVVV
    这样uv不在同一行了。

所以采用每个分量单独的纹理。这样厉害的地方就是他们可以共用同一个纹理坐标:

glBindTexture(GL_TEXTURE_2D, textures[0]);glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, overlay->pixels[0]);glGenerateMipmap(GL_TEXTURE_2D);glBindTexture(GL_TEXTURE_2D, textures[1]);glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width/2, height/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, overlay->pixels[1]);glGenerateMipmap(GL_TEXTURE_2D);glBindTexture(GL_TEXTURE_2D, textures[2]);glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width/2, height/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, overlay->pixels[2]);glGenerateMipmap(GL_TEXTURE_2D);
  • 3个纹理,y的纹理和图像大小一样,u和v的高宽都减半。
  • overlay只是用来打包视频帧数据的一个结构体,pixels的0、1、2分别就是yuv3个分量的平面的开始位置。
  • 有一个关键点是纹理格式使用GL_LUMINANCE,也就是单颜色通道。看网上的例子,之前写的是GL_RED的是不行的。
  • 因为威力坐标是一个相对坐标,是映射到[0, 1]范围内的。所以对于纹理坐标[x, y],在u和v纹理的上取到的点跟y纹理坐标上[2x, 2y]是对应的,而这正是yuv420需要的:4个y对应一组uv。

最后用的把yuv转成rgb,用的公式:

R = Y + 1.402 (Cr-128)
G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
B = Y + 1.772 (Cb-128)

这里还有一个注意的就是,YUV和YCrCb的区别
YCrCb是YUV的一个偏移版本,所以需要减去0.5(因为都映射到0-1范围了128就是0.5)。当然我觉得这个公式还是要看编码的时候设置了什么格式,视频拍摄的时候是怎么把rgb转成yuv的,两者配套就ok了!

绘制正方形

glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer);glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);_frameProgram->use();_frameProgram->setTexture("yPlaneTex", GL_TEXTURE_2D, textures[0], 0);_frameProgram->setTexture("uPlaneTex", GL_TEXTURE_2D, textures[1], 1);_frameProgram->setTexture("vPlaneTex", GL_TEXTURE_2D, textures[2], 2);glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);glBindRenderbuffer(GL_RENDERBUFFER, self.colorBuffer);[self.context presentRenderbuffer:GL_RENDERBUFFER];
  • 开启program,并把三个纹理输入
  • 使用GL_TRIANGLE_STRIP绘制,这样可以更简单些,用GL_TRIANGLES就得两个三角形了。因为这个,所以vertices的4个点是左上、左下、右上、右下的顺序,具体规律看【OpenGL】理解GL_TRIANGLE_STRIP等绘制三角形序列的三种方式。

细节处理

  • 监测一下app前后台切换,后台就不要渲染了:
[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(catchAppResignActive) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(catchAppBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
......
-(void)catchAppResignActive{_appIsUnactive = YES;
}-(void)catchAppBecomeActive{_appIsUnactive = NO;
}
.......
if (self.appIsUnactive) {return;    //绘制之前检查,直接取消
}
  • 把绘制移到副线程
    iOS中OpenGL ES的的这些操纵是可以全部放到副线程处理的,包括最后的presentRenderbuffer。关键是context构建、数组准备(VAO texture等)、渲染这些得在一个线程里,当然也可以多线程操作,但对于视屏播放而言没有必要,去除没必要的性能消耗吧,锁都不用加了。

  • layer的frame改变处理

-(void)layoutSubviews{[super layoutSubviews];//If context has setuped and layer's size has changed, realloc renderBuffer.if (self.context && !CGSizeEqualToSize(self.layer.frame.size, self.bufferSize)) {_needReallocRenderBuffer = YES;}
}
...........
if (_needReallocRenderBuffer) {[self reallocRenderBuffer];_needReallocRenderBuffer = NO;
}
.........
-(void)reallocRenderBuffer{glBindRenderbuffer(GL_RENDERBUFFER, _colorBuffer);[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_renderLayer];glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorBuffer);......
}
  • 改变之后,重新分配render buffer的内存
  • 为了在同一个线程里处理,所以没有直接在layoutSubviews里重新分配render buffer,这里肯定是主线程。所以只是做了个标记
  • 在渲染的方法里,先查看_needReallocRenderBuffer,然后realloc render buffer.

最后

重点是fragment shader里对yuv分量的读取:

  1. 采取3个纹理
  2. 使用同一个纹理坐标
  3. 构建纹理是使用GL_LUMINANCE, u、v纹理宽高相对y都减半。


作者:find_1991
链接:http://www.jianshu.com/p/9e5588bb4c2f
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关文章:

C++类的静态成员详细讲解

在C中&#xff0c;静态成员是属于整个类的而不是某个对象&#xff0c;静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则&#xff0c;保证了安全性还可以节省内存。 静态成员的定义或声明要…

jenkins2 multibranch

通过multibranch类型的pipeline job使得对于多个branch的支持更加简单。只需要创建一个multibranch job&#xff0c;jenkins将自动地为所有的branch创建job。 文章来自&#xff1a;http://www.ciandcd.com文中的代码来自可以从github下载&#xff1a; https://github.com/ciand…

Nagios的安装和基本配置(一:知识点总结及环境准备)

实验目的及要求 掌握Nagios监控的基本使用&#xff1b;掌握Nagios监控服务的搭建和配置&#xff1b; 实验环境&#xff1a; 1、满足实验要求的PC端&#xff1b; Host-name OS IP sofaware Nagios-server Centos7 192.168.1.119 Apache,php,Nagios,Nagios-plguins Nag…

浅谈Android四大组件之Service

一&#xff1a;Service简介 Android开发中&#xff0c;当需要创建在后台运行的程序的时候&#xff0c;就要使用到Service。 1:Service&#xff08;服务&#xff09;是一个没有用户界面的在后台运行执行耗时操作的应用组件。其他应用组件能够启动Service&#xff0c;并且当用户切…

使用 fastlane 实现 iOS 持续集成(二)

本文接上篇文章主要说下怎样使用 fastlane 上传到fir和蒲公英&#xff0c;下面先介绍下 plugin 命令。 plugin命令介绍: 列出所有可用插件 fastlane search_plugins 搜索指定名称的插件: fastlane search_plugins [query] 添加插件: fastlane add_plugin [name] 安装插件: fast…

Nagios的安装和基本配置(二:Nagios-Server的安装)

任务二、Nagios-server的安装 2.1、创建Nagios用户和组 注&#xff1a; #useradd Nagios -s /bin/nologin #groundadd nagcmd #usermod -a -G nagcmd Nagios #usermod -a G nagcmd apache 2.2、安装Nagios 2.2.1、上传软件包至操作系统中&#xff1b; 2.2.2、解压软件并…

shell编程-正则表达式

1.正则表达式是什么 它主要用于字符串的模式分割&#xff0c;匹配&#xff0c;查找及替换操作。 2、正则表达式与通配符 正则表达式用来在文件中匹配符合条件的字符串&#xff0c;正则包含匹配。grep,awk,sed等命令可以支持正则表达式。 通配符用来匹配符合条件的文件名&#x…

使用 CocoaPods 给微信集成 SDK 打印收发消息

推荐序 本文介绍的是一套逆向工具&#xff0c;可以在非越狱手机上给任意应用增加插件。在文末的示例中&#xff0c;作者拿微信举例&#xff0c;展示出在微信中打印收发消息的功能。 这套工具可以加快逆向开发的速度&#xff0c;其重签名思想也可以用于二次分发别人的应用。 其实…

数据库之子查询四(多重,表复制)

一、多重子查询 select teaID,teaName,age,sex,dept,professionfrom tteacherwhere dept(select dept from teaIDt103265)and profession(select professionfrom tteacherwhere teaIDt103265)这里的子查询就是为了从表中提取出有效信息参与外部查询二、create table 语句中子查…

Nagios的安装和基本配置(三:Nagios-Client的安装)

任务三、Nagios-Client的安装 3.1、关闭防火墙和selinux 注&#xff1a; #systemctl stop firewalld.service #systemctl disable firewalld.service #vi /etc/selinux/config 3.2、配置环境 #yum install gcc glibc-common -y #yum install gd gd-devel openssl openssl…

事件(待完成)

内容窗口 事件绑定​ 给整个浏览器 内容窗口区的事件绑定。 ​通过 document.documentElement或者document.body&#xff1f;似乎都可以。但最好是直接通过document document.addEventListener(mousemove,function () { });// 整个浏览器内容范围都将触发。拖动实现必用​ 转载…

iOS 模仿支付宝支付到账推送,播报钱数

最近申请了支付宝的二维码收钱码&#xff0c;其中支付宝有这么一个功能&#xff0c;就是&#xff0c;别人扫描你的二维码给你转账之后&#xff0c;收到钱会有一条语音推送&#xff0c;”支付宝到账 1000万“之类的推送消息&#xff0c;不管你的支付宝app有没有被杀死。 只要你的…

hdu - 4707 - Pet

题意&#xff1a;一棵N个结点(编号从0开始)的树&#xff0c;根结点为0&#xff0c;求到根结点的距离大于D的结点个数&#xff08;0 < 测试组数T < 10, 0<N<100000, 0<D<N&#xff09;。 题目链接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid4707…

Nagios的安装和基本配置(四:调试验证 错误总结)

任务四、调试验证 4.1、验证连通性 在/usr/local/Nagios/etc/nrpe.cfg文件中server的ip地址 #vi /usr/local/Nagios/etc/nrpe.cfg #重启nrpe #pkill nrpe #netstat -Intp #/usr/local/Nagios/bin/nrpe -d -c /usr/local/Nagios/etc/nrpe.cfg #在server主机做验证 #cd /…

hitTest和pointInside方法

hittest方法 就是用来寻找最合适的view当一个事件传递给一个控件&#xff0c;就会调用这个控件的hitTest方法点击了白色的view&#xff1a; 触摸事件 -> UIApplication -> UIWindow 调用 [UIWindow hitTest] -> 白色view [WhteView hitTest] 实验1: 定义 BaseView&…

Github上的PHP资源汇总大全

依赖管理 ——用于依赖管理的包和框架 Composer/Packagist : 一个包和依赖管理器 Composer Installers: 一个多框架Composer库安装器 Pickle: 可以在任意平台上安装PHP扩展包 依赖管理的附加部分 ——其它依赖管理的相关工具 Satis : 静态的Composer库生成器 Composition: 一个…

UIButton长按事件

添加长按事件1 - (void)viewDidLoad2 {3 [super viewDidLoad];4 //Do any additional setup after loading the view, typically from a nib.5 6 UIButton *aBtn[UIButton buttonWithType:UIButtonTypeRoundedRect];7 [aBtn setFrame:CGRectMake(0, 10, 60, 60…

Hadoop集群搭建(一:集群安装及网络环境配置)

实验目的及要求 完成VMware workstations安装&#xff0c;会应用相关操作&#xff1b;完成虚拟机中Linux CentOS 7操作系统安装&#xff1b;完成静态网络地址的配置&#xff0c;所有主机的网络能够正常使用&#xff0c;相互之间能够正常连接&#xff1b;完成主机名配置&#x…

QQ音乐API分析记录

我一直是QQ音乐的用户&#xff0c;最近想做一个应用&#xff0c;想用QQ音乐的API&#xff0c;搜索了很久无果&#xff0c;于是就自己分析QQ音乐的API。 前不久发现QQ音乐出了网页版的&#xff0c;是Flash的&#xff0c;但是&#xff0c;我用iPhone打开这个链接的时候&#xff0…

Vision 圖像識別框架的使用

阅读 137收藏 102017-10-18原文链接&#xff1a;www.itread01.comGoogle无人车之父、MIT/斯坦福/耶鲁专家带你进入无人驾驶之域 http://cn.udacity.com/course/intro-to-self-driving-cars--nd113 本文為CocoaChina網友 品位生活 投稿 北京時間2017.6.6日淩晨1點&#xff0c;新…

Jmeter性能测试 入门

Jmeter性能测试 入门 原文:Jmeter性能测试 入门Jmeter是一款优秀的开源测试工具&#xff0c; 是每个资深测试工程师&#xff0c;必须掌握的测试工具&#xff0c;熟练使用Jmeter能大大提高工作效率。 熟练使用Jmeter后&#xff0c; 能用Jmeter搞定的事情&#xff0c;你就不会使用…

Hadoop集群搭建(二:集群主机间免密登录配置)

实验目的及要求&#xff1a; 静态网络地址配置&#xff1b;主机名的配置&#xff1b;防火墙的配置&#xff0c;使平台相关软件的常用端口能够远程正常访问&#xff1b;主机地址映射的配置&#xff0c;使所有主机能够通过主机名相互正常访问&#xff1b;免密码登录的配置&#…

你真的会用 CocoaPods 吗?

CocoaPods 可以说是 iOS 开发应用最广泛的包管理工具&#xff0c;本篇文章主要介绍 CocoaPods 的第三方库是怎样从网络集成到我们本地的项目当中&#xff0c;也是制作私有库、开源库和 iOS 项目组件化的一个知识铺垫。 让我们从一张图片开始&#xff1a; CocoaPods 工作流程 …

【spring 5】AOP:spring中对于AOP的的实现

在前两篇博客中&#xff0c;介绍了AOP实现的基础&#xff1a;静态代理和动态代理&#xff0c;这篇博客介绍spring中AOP的实现。 一、采用Annotation方式 首先引入jar包&#xff1a;aspectjrt.jar && aspectweaver.jar applicationContext配置文件&#xff1a; <span…

通过BeanShell获取UUID并将参数传递给Jmeter

有些HTTPS请求报文的报文体中包含由客户端生成的UUID&#xff0c;在用Jmeter做接口自动化测试的时候&#xff0c;因为越过了客户端&#xff0c;直接向服务器端发送报文&#xff0c;所以&#xff0c;需要在Jmeter中通过beanshell获取UUID&#xff0c;并能将参数传递给Jmeter&…

Hadoop集群搭建(四:Zookeeper环境安装)

实验 目的 要求 目的&#xff1a; 1、掌握在完全分布模式的整合平台中Zookeeper的完全分布模式的安装 要求&#xff1a; 完成Zookeeper的完全分布农事的安装&#xff1b;Zookeeper服务能够正常启动和连接&#xff1b;Zookeeper控制台能够正常进入&#xff1b;Zookeeper控制台命…

史上第二走心的 iOS11-Drag Drop 教程

原文链接&#xff1a;www.jianshu.com只需完成个人实名注册&#xff0c;即可获得腾讯云免费套餐&#xff01;云服务器CVM、云数据库 MYSQL、文件存储 CFS.....应有尽有&#xff01;https://cloud.tencent.com/act/free 话不多说&#xff0c;先上效果图 普通view拖拽效果 TableV…

指针的各式定义

1&#xff0c;一个整形数&#xff1a; int a; 2&#xff0c;一个指向整形数的指针&#xff1a; int *a; 3&#xff0c;一个指向指针的指针&#xff0c;它指向的指针指向一个整形数&#xff1a; int **a; 4&#xff0c;一个有10个整形数的数组&#xff1a; int a[10]; 5&#…

AS 400 常用命令

转自&#xff1a;http://blog.chinaunix.net/uid-22375044-id-3049793.html 一、命令技巧 命令构成&#xff1a; CRT* (Creat) 创建 WRK* (Work With) 操作 RMV* (Remove) 去除 DSP* (Display) 显示 ADD* (Add) 添加 CHG* (Change) 改变 DLT* (Delete) 删除 CFG* (Config) 配置…

Hadoop集群搭建(五:Hadoop HA集群模式的安装)

实验 目的 要求 目的&#xff1a; 1、Hadoop的高可用完全分布模式的安装和验证 要求&#xff1a; 完成Hadoop的高可用完全分布模式的安装&#xff1b;Hadoop的相关服务进程能够正常的启动&#xff1b;HDFS能够正常的使用&#xff1b;MapReduce示例程序能够正常运行&#xff1b…