最新要闻
- 理想车主夜间驾车中控显示有人追车?官方回应:视觉感知算法Bug
- 今日精选:一驾校打广告称不识字也能考驾照:3次考不过退费
- 55%~100%五种浓度:怡浓纯黑巧克力35元起400g大促
- 环球通讯!开电动车返乡要三思 网友晒高速服务区充电现状:已排成长队
- 联想小新Pro超能本2023升级140W快充:半小时就可回血66%
- 前沿资讯!2023年了 游戏还在背锅
- 第一款8K显示器发售6年:居然没人接班了
- 天天快资讯:丈夫称老家有别墅 女子回村直呼上当:网友调侃诚不欺我 真纯天然
- 被中国游客狠狠抛弃:韩国人口连续三年减少 女多男少拉不动内需
- 环球播报:便宜了2.5元 2023年春节档电影票价格7年来首次下降:你愿意去看吗?
- 合资家轿之王!新款日产轩逸e-POWER曝光:百公里仅需4升油
- 今热点:一箭14星成功升空 卫星首图回传:路面汽车清晰可见
- 皮俑是什么做成的?皮俑为什么救吴邪?
- 擅长画虎的画家是?擅长画虎的十大画家
- 神奇宝贝的观看顺序是什么?神奇宝贝的大结局是什么?
- 用上半固态电池!赛力斯发布海外新车型SERES 5:订单超2万
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
Opengl ES之RGB转NV21
前言
在上一篇理论文章中我们介绍了YUV到RGB之间转换的几种公式与一些优化算法,今天我们再来介绍一下RGB到YUV的转换,顺便使用Opengl ES做个实践,将一张RGB的图片通过Shader的方式转换YUV格式图,然后保存到本地。
可能有的童鞋会问,YUV转RGB是为了渲染显示,那么RGB转YUV的应用场景是什么?在做视频编码的时候我们可以使用MediaCodec搭配Surface就可以完成,貌似也没有用到RGB转YUV的功能啊,硬编码没有用到,那么软编码呢?一般我们做视频编码的时候都是硬编码优先,软编码兜底的原则,在遇到一些硬编码不可用的情况下可能就需要用到x264库进行软编码了,而此时RGB转YUV可能就派上用场啦。
(资料图片仅供参考)
RGB到YUV的转换公式
在前面 Opengl ES之YUV数据渲染 一文中我们介绍过YUV的几种兼容标准,下面我们看看RGB到YUV的转换公式:
RGB 转 BT.601 YUV
Y = 0.257R + 0.504G + 0.098B + 16Cb = -0.148R - 0.291G + 0.439B + 128Cr = 0.439R - 0.368G - 0.071B + 128
RGB 转 BT.709 YUV
Y = 0.183R + 0.614G + 0.062B + 16Cb = -0.101R - 0.339G + 0.439B + 128Cr = 0.439R - 0.399G - 0.040B + 128
或者也可以使用矩阵运算的方式进行转换,更加的便捷:
RGB转YUV
先说一下RGB转YUV的过程,先将RGB数据按照公式转换为YUV数据,然后将YUV数据按照RGBA进行排布,这一步的目的是为了后续数据读取,最后使用glReadPixels
读取YUV数据。
而对于OpenGL ES来说,目前它输入只认RGBA、lumiance、luminace alpha这几个格式,输出大多数实现只认RGBA格式,因此输出的数据格式虽然是YUV格式,但是在存储时我们仍然要按照RGBA方式去访问texture数据。
以NV21的YUV数据为例,它的内存大小为width x height * 3 / 2
。如果是RGBA的格式存储的话,占用的内存空间大小是width x height x 4
(因为 RGBA 一共4个通道)。很显然它们的内存大小是对不上的,那么该如何调整Opengl buffer的大小让RGBA的输出能对应上YUV的输出呢?我们可以设计输出的宽为width / 4
,高为height * 3 / 2
即可。
为什么是这样的呢?虽然我们的目的是将RGB转换成YUV,但是我们的输入和输出时读取的类型GLenum是依然是RGBA,也就是说:width x height x 4 = (width / 4) x (height * 3 / 2) * 4
而YUV数据在内存中的分布以下这样子的:
width / 4|--------------|| || | h| Y ||--------------| | U | V || | | h / 2|--------------|
那么上面的排序如果进行了归一化之后呢,就变成了下面这样子了:
(0,0) width / 4 (1,0)|--------------|| || | h| Y ||--------------| (1,2/3) | U | V || | | h / 2|--------------|(0,1) (1,1)
从上面的排布可以看出看出,在纹理坐标y < (2/3)
时,需要完成一次对整个纹理的采样,用于生成Y数据,当纹理坐标 y > (2/3)
时,同样需要再进行一次对整个纹理的采样,用于生成UV的数据。同时还需要将我们的视窗设置为glViewport(0, 0, width / 4, height * 1.5);
由于视口宽度设置为原来的 1/4 ,可以简单的认为相对于原来的图像每隔4个像素做一次采样,由于我们生成Y数据是要对每一个像素都进行采样,所以还需要进行3次偏移采样。
同理,生成对于UV数据也需要进行3次额外的偏移采样。
在着色器中offset变量需要设置为一个归一化之后的值:1.0/width
, 按照原理图,在纹理坐标 y < (2/3) 范围,一次采样(加三次偏移采样)4 个 RGBA 像素(R,G,B,A)生成 1 个(Y0,Y1,Y2,Y3),整个范围采样结束时填充好 width*height
大小的缓冲区;当纹理坐标 y > (2/3) 范围,一次采样(加三次偏移采样)4 个 RGBA 像素(R,G,B,A)生成 1 个(V0,U0,V0,U1),又因为 UV 缓冲区的高度为 height/2 ,VU plane 在垂直方向的采样是隔行进行,整个范围采样结束时填充好 width*height/2
大小的缓冲区。
主要代码
RGBtoYUVOpengl.cpp#include "../utils/Log.h"#include "RGBtoYUVOpengl.h"// 顶点着色器static const char *ver = "#version 300 es\n" "in vec4 aPosition;\n" "in vec2 aTexCoord;\n" "out vec2 v_texCoord;\n" "void main() {\n" " v_texCoord = aTexCoord;\n" " gl_Position = aPosition;\n" "}";// 片元着色器static const char *fragment = "#version 300 es\n" "precision mediump float;\n" "in vec2 v_texCoord;\n" "layout(location = 0) out vec4 outColor;\n" "uniform sampler2D s_TextureMap;\n" "uniform float u_Offset;\n" "const vec3 COEF_Y = vec3(0.299, 0.587, 0.114);\n" "const vec3 COEF_U = vec3(-0.147, -0.289, 0.436);\n" "const vec3 COEF_V = vec3(0.615, -0.515, -0.100);\n" "const float UV_DIVIDE_LINE = 2.0 / 3.0;\n" "void main(){\n" " vec2 texelOffset = vec2(u_Offset, 0.0);\n" " if (v_texCoord. y <= UV_DIVIDE_LINE) {\n" " vec2 texCoord = vec2(v_texCoord. x, v_texCoord. y * 3.0 / 2.0);\n" " vec4 color0 = texture(s_TextureMap, texCoord);\n" " vec4 color1 = texture(s_TextureMap, texCoord + texelOffset);\n" " vec4 color2 = texture(s_TextureMap, texCoord + texelOffset * 2.0);\n" " vec4 color3 = texture(s_TextureMap, texCoord + texelOffset * 3.0);\n" " float y0 = dot(color0. rgb, COEF_Y);\n" " float y1 = dot(color1. rgb, COEF_Y);\n" " float y2 = dot(color2. rgb, COEF_Y);\n" " float y3 = dot(color3. rgb, COEF_Y);\n" " outColor = vec4(y0, y1, y2, y3);\n" " } else {\n" " vec2 texCoord = vec2(v_texCoord.x, (v_texCoord.y - UV_DIVIDE_LINE) * 3.0);\n" " vec4 color0 = texture(s_TextureMap, texCoord);\n" " vec4 color1 = texture(s_TextureMap, texCoord + texelOffset);\n" " vec4 color2 = texture(s_TextureMap, texCoord + texelOffset * 2.0);\n" " vec4 color3 = texture(s_TextureMap, texCoord + texelOffset * 3.0);\n" " float v0 = dot(color0. rgb, COEF_V) + 0.5;\n" " float u0 = dot(color1. rgb, COEF_U) + 0.5;\n" " float v1 = dot(color2. rgb, COEF_V) + 0.5;\n" " float u1 = dot(color3. rgb, COEF_U) + 0.5;\n" " outColor = vec4(v0, u0, v1, u1);\n" " }\n" "}";// 使用绘制两个三角形组成一个矩形的形式(三角形带)// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形const static GLfloat VERTICES[] = { 1.0f,-1.0f, // 右下 1.0f,1.0f, // 右上 -1.0f,-1.0f, // 左下 -1.0f,1.0f // 左上};// FBO贴图纹理坐标(参考手机屏幕坐标系统,原点在左下角)// 注意坐标不要错乱const static GLfloat TEXTURE_COORD[] = { 1.0f,0.0f, // 右下 1.0f,1.0f, // 右上 0.0f,0.0f, // 左下 0.0f,1.0f // 左上};RGBtoYUVOpengl::RGBtoYUVOpengl() { initGlProgram(ver,fragment); positionHandle = glGetAttribLocation(program,"aPosition"); textureHandle = glGetAttribLocation(program,"aTexCoord"); textureSampler = glGetUniformLocation(program,"s_TextureMap"); u_Offset = glGetUniformLocation(program,"u_Offset"); LOGD("program:%d",program); LOGD("positionHandle:%d",positionHandle); LOGD("textureHandle:%d",textureHandle); LOGD("textureSample:%d",textureSampler); LOGD("u_Offset:%d",u_Offset);}RGBtoYUVOpengl::~RGBtoYUVOpengl() noexcept {}void RGBtoYUVOpengl::fboPrepare() { glGenTextures(1, &fboTextureId); // 绑定纹理 glBindTexture(GL_TEXTURE_2D, fboTextureId); // 为当前绑定的纹理对象设置环绕、过滤方式 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, GL_NONE); glGenFramebuffers(1,&fboId); glBindFramebuffer(GL_FRAMEBUFFER,fboId); // 绑定纹理 glBindTexture(GL_TEXTURE_2D,fboTextureId); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTextureId, 0); // 这个纹理是多大的? glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imageWidth / 4, imageHeight * 1.5, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); // 检查FBO状态 if (glCheckFramebufferStatus(GL_FRAMEBUFFER)!= GL_FRAMEBUFFER_COMPLETE) { LOGE("FBOSample::CreateFrameBufferObj glCheckFramebufferStatus status != GL_FRAMEBUFFER_COMPLETE"); } // 解绑 glBindTexture(GL_TEXTURE_2D, GL_NONE); glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);}// 渲染逻辑void RGBtoYUVOpengl::onDraw() { // 绘制到FBO上去 // 绑定fbo glBindFramebuffer(GL_FRAMEBUFFER, fboId); glPixelStorei(GL_UNPACK_ALIGNMENT,1); // 设置视口大小 glViewport(0, 0,imageWidth / 4, imageHeight * 1.5); glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program); // 激活纹理 glActiveTexture(GL_TEXTURE2); glUniform1i(textureSampler, 2); // 绑定纹理 glBindTexture(GL_TEXTURE_2D, textureId); // 设置偏移 float texelOffset = (float) (1.f / (float) imageWidth); glUniform1f(u_Offset,texelOffset); /** * size 几个数字表示一个点,显示是两个数字表示一个点 * normalized 是否需要归一化,不用,这里已经归一化了 * stride 步长,连续顶点之间的间隔,如果顶点直接是连续的,也可填0 */ // 启用顶点数据 glEnableVertexAttribArray(positionHandle); glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES); // 纹理坐标 glEnableVertexAttribArray(textureHandle); glVertexAttribPointer(textureHandle,2,GL_FLOAT,GL_FALSE,0,TEXTURE_COORD); // 4个顶点绘制两个三角形组成矩形 glDrawArrays(GL_TRIANGLE_STRIP,0,4); glUseProgram(0); // 禁用顶点 glDisableVertexAttribArray(positionHandle); if(nullptr != eglHelper){ eglHelper->swapBuffers(); } glBindTexture(GL_TEXTURE_2D, 0); // 解绑fbo glBindFramebuffer(GL_FRAMEBUFFER, 0);}// 设置RGB图像数据void RGBtoYUVOpengl::setPixel(void *data, int width, int height, int length) { LOGD("texture setPixel"); imageWidth = width; imageHeight = height; // 准备fbo fboPrepare(); glGenTextures(1, &textureId); // 激活纹理,注意以下这个两句是搭配的,glActiveTexture激活的是那个纹理,就设置的sampler2D是那个 // 默认是0,如果不是0的话,需要在onDraw的时候重新激活一下?// glActiveTexture(GL_TEXTURE0);// glUniform1i(textureSampler, 0);// 例如,一样的 glActiveTexture(GL_TEXTURE2); glUniform1i(textureSampler, 2); // 绑定纹理 glBindTexture(GL_TEXTURE_2D, textureId); // 为当前绑定的纹理对象设置环绕、过滤方式 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // 生成mip贴图 glGenerateMipmap(GL_TEXTURE_2D); // 解绑定 glBindTexture(GL_TEXTURE_2D, 0);}// 读取渲染后的YUV数据void RGBtoYUVOpengl::readYUV(uint8_t **data, int *width, int *height) { // 从fbo中读取 // 绑定fbo *width = imageWidth; *height = imageHeight; glBindFramebuffer(GL_FRAMEBUFFER, fboId); glBindTexture(GL_TEXTURE_2D, fboTextureId); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTextureId, 0); *data = new uint8_t[imageWidth * imageHeight * 3 / 2]; glReadPixels(0, 0, imageWidth / 4, imageHeight * 1.5, GL_RGBA, GL_UNSIGNED_BYTE, *data); glBindTexture(GL_TEXTURE_2D, 0); // 解绑fbo glBindFramebuffer(GL_FRAMEBUFFER, 0);}
下面是Activity的主要代码逻辑:
public class RGBToYUVActivity extends AppCompatActivity { protected MyGLSurfaceView myGLSurfaceView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_rgb_to_yuv); myGLSurfaceView = findViewById(R.id.my_gl_surface_view); myGLSurfaceView.setOpenGlListener(new MyGLSurfaceView.OnOpenGlListener() { @Override public BaseOpengl onOpenglCreate() { return new RGBtoYUVOpengl(); } @Override public Bitmap requestBitmap() { BitmapFactory.Options options = new BitmapFactory.Options(); options.inScaled = false; return BitmapFactory.decodeResource(getResources(),R.mipmap.ic_smile,options); } @Override public void readPixelResult(byte[] bytes) { if (null != bytes) { } } // 也就是RGBtoYUVOpengl::readYUV读取到结果数据回调 @Override public void readYUVResult(byte[] bytes) { if (null != bytes) { String fileName = System.currentTimeMillis() + ".yuv"; File fileParent = getFilesDir(); if (!fileParent.exists()) { fileParent.mkdirs(); } FileOutputStream fos = null; try { File file = new File(fileParent, fileName); fos = new FileOutputStream(file); fos.write(bytes,0,bytes.length); fos.flush(); fos.close(); Toast.makeText(RGBToYUVActivity.this, "YUV图片保存成功" + file.getAbsolutePath(), Toast.LENGTH_LONG).show(); } catch (Exception e) { Log.v("fly_learn_opengl", "图片保存异常:" + e.getMessage()); Toast.makeText(RGBToYUVActivity.this, "YUV图片保存失败", Toast.LENGTH_LONG).show(); } } } }); Button button = findViewById(R.id.bt_rgb_to_yuv); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { myGLSurfaceView.readYuvData(); } }); ImageView iv_rgb = findViewById(R.id.iv_rgb); iv_rgb.setImageResource(R.mipmap.ic_smile); }}
以下是自定义SurfaceView的代码:
public class MyGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback { private final static int MSG_CREATE_GL = 101; private final static int MSG_CHANGE_GL = 102; private final static int MSG_DRAW_GL = 103; private final static int MSG_DESTROY_GL = 104; private final static int MSG_READ_PIXEL_GL = 105; private final static int MSG_UPDATE_BITMAP_GL = 106; private final static int MSG_UPDATE_YUV_GL = 107; private final static int MSG_READ_YUV_GL = 108; public BaseOpengl baseOpengl; private OnOpenGlListener onOpenGlListener; private HandlerThread handlerThread; private Handler renderHandler; public int surfaceWidth; public int surfaceHeight; public MyGLSurfaceView(Context context) { this(context,null); } public MyGLSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); getHolder().addCallback(this); handlerThread = new HandlerThread("RenderHandlerThread"); handlerThread.start(); renderHandler = new Handler(handlerThread.getLooper()){ @Override public void handleMessage(@NonNull Message msg) { switch (msg.what){ case MSG_CREATE_GL: baseOpengl = onOpenGlListener.onOpenglCreate(); Surface surface = (Surface) msg.obj; if(null != baseOpengl){ baseOpengl.surfaceCreated(surface); Bitmap bitmap = onOpenGlListener.requestBitmap(); if(null != bitmap){ baseOpengl.setBitmap(bitmap); } } break; case MSG_CHANGE_GL: if(null != baseOpengl){ Size size = (Size) msg.obj; baseOpengl.surfaceChanged(size.getWidth(),size.getHeight()); } break; case MSG_DRAW_GL: if(null != baseOpengl){ baseOpengl.onGlDraw(); } break; case MSG_READ_PIXEL_GL: if(null != baseOpengl){ byte[] bytes = baseOpengl.readPixel(); if(null != bytes && null != onOpenGlListener){ onOpenGlListener.readPixelResult(bytes); } } break; case MSG_READ_YUV_GL: if(null != baseOpengl){ byte[] bytes = baseOpengl.readYUVResult(); if(null != bytes && null != onOpenGlListener){ onOpenGlListener.readYUVResult(bytes); } } break; case MSG_UPDATE_BITMAP_GL: if(null != baseOpengl){ Bitmap bitmap = onOpenGlListener.requestBitmap(); if(null != bitmap){ baseOpengl.setBitmap(bitmap); baseOpengl.onGlDraw(); } } break; case MSG_UPDATE_YUV_GL: if(null != baseOpengl){ YUVBean yuvBean = (YUVBean) msg.obj; if(null != yuvBean){ baseOpengl.setYuvData(yuvBean.getyData(),yuvBean.getUvData(),yuvBean.getWidth(),yuvBean.getHeight()); baseOpengl.onGlDraw(); } } break; case MSG_DESTROY_GL: if(null != baseOpengl){ baseOpengl.surfaceDestroyed(); } break; } } }; } public void setOpenGlListener(OnOpenGlListener listener) { this.onOpenGlListener = listener; } @Override public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { Message message = Message.obtain(); message.what = MSG_CREATE_GL; message.obj = surfaceHolder.getSurface(); renderHandler.sendMessage(message); } @Override public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int w, int h) { Message message = Message.obtain(); message.what = MSG_CHANGE_GL; message.obj = new Size(w,h); renderHandler.sendMessage(message); Message message1 = Message.obtain(); message1.what = MSG_DRAW_GL; renderHandler.sendMessage(message1); surfaceWidth = w; surfaceHeight = h; } @Override public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) { Message message = Message.obtain(); message.what = MSG_DESTROY_GL; renderHandler.sendMessage(message); } public void readGlPixel(){ Message message = Message.obtain(); message.what = MSG_READ_PIXEL_GL; renderHandler.sendMessage(message); } public void readYuvData(){ Message message = Message.obtain(); message.what = MSG_READ_YUV_GL; renderHandler.sendMessage(message); } public void updateBitmap(){ Message message = Message.obtain(); message.what = MSG_UPDATE_BITMAP_GL; renderHandler.sendMessage(message); } public void setYuvData(byte[] yData,byte[] uvData,int width,int height){ Message message = Message.obtain(); message.what = MSG_UPDATE_YUV_GL; message.obj = new YUVBean(yData,uvData,width,height); renderHandler.sendMessage(message); } public void release(){ // todo 主要线程同步问题,当心surfaceDestroyed还没有执行到,但是就被release了,那就内存泄漏了 if(null != baseOpengl){ baseOpengl.release(); } } public void requestRender(){ Message message = Message.obtain(); message.what = MSG_DRAW_GL; renderHandler.sendMessage(message); } public interface OnOpenGlListener{ BaseOpengl onOpenglCreate(); Bitmap requestBitmap(); void readPixelResult(byte[] bytes); void readYUVResult(byte[] bytes); }}
BaseOpengl的java代码:
public class BaseOpengl { public static final int YUV_DATA_TYPE_NV12 = 0; public static final int YUV_DATA_TYPE_NV21 = 1; // 三角形 public static final int DRAW_TYPE_TRIANGLE = 0; // 四边形 public static final int DRAW_TYPE_RECT = 1; // 纹理贴图 public static final int DRAW_TYPE_TEXTURE_MAP = 2; // 矩阵变换 public static final int DRAW_TYPE_MATRIX_TRANSFORM = 3; // VBO/VAO public static final int DRAW_TYPE_VBO_VAO = 4; // EBO public static final int DRAW_TYPE_EBO_IBO = 5; // FBO public static final int DRAW_TYPE_FBO = 6; // PBO public static final int DRAW_TYPE_PBO = 7; // YUV nv12与nv21渲染 public static final int DRAW_YUV_RENDER = 8; // 将rgb图像转换城nv21 public static final int DRAW_RGB_TO_YUV = 9; public long glNativePtr; protected EGLHelper eglHelper; protected int drawType; public BaseOpengl(int drawType) { this.drawType = drawType; this.eglHelper = new EGLHelper(); } public void surfaceCreated(Surface surface) { Log.v("fly_learn_opengl","------------surfaceCreated:" + surface); eglHelper.surfaceCreated(surface); } public void surfaceChanged(int width, int height) { Log.v("fly_learn_opengl","------------surfaceChanged:" + Thread.currentThread()); eglHelper.surfaceChanged(width,height); } public void surfaceDestroyed() { Log.v("fly_learn_opengl","------------surfaceDestroyed:" + Thread.currentThread()); eglHelper.surfaceDestroyed(); } public void release(){ if(glNativePtr != 0){ n_free(glNativePtr,drawType); glNativePtr = 0; } } public void onGlDraw(){ Log.v("fly_learn_opengl","------------onDraw:" + Thread.currentThread()); if(glNativePtr == 0){ glNativePtr = n_gl_nativeInit(eglHelper.nativePtr,drawType); } if(glNativePtr != 0){ n_onGlDraw(glNativePtr,drawType); } } public void setBitmap(Bitmap bitmap){ if(glNativePtr == 0){ glNativePtr = n_gl_nativeInit(eglHelper.nativePtr,drawType); } if(glNativePtr != 0){ n_setBitmap(glNativePtr,bitmap); } } public void setYuvData(byte[] yData,byte[] uvData,int width,int height){ if(glNativePtr != 0){ n_setYuvData(glNativePtr,yData,uvData,width,height,drawType); } } public void setMvpMatrix(float[] mvp){ if(glNativePtr == 0){ glNativePtr = n_gl_nativeInit(eglHelper.nativePtr,drawType); } if(glNativePtr != 0){ n_setMvpMatrix(glNativePtr,mvp); } } public byte[] readPixel(){ if(glNativePtr != 0){ return n_readPixel(glNativePtr,drawType); } return null; } public byte[] readYUVResult(){ if(glNativePtr != 0){ return n_readYUV(glNativePtr,drawType); } return null; } // 绘制 private native void n_onGlDraw(long ptr,int drawType); private native void n_setMvpMatrix(long ptr,float[] mvp); private native void n_setBitmap(long ptr,Bitmap bitmap); protected native long n_gl_nativeInit(long eglPtr,int drawType); private native void n_free(long ptr,int drawType); private native byte[] n_readPixel(long ptr,int drawType); private native byte[] n_readYUV(long ptr,int drawType); private native void n_setYuvData(long ptr,byte[] yData,byte[] uvData,int width,int height,int drawType);}
将转换后的YUV数据读取保存好后,可以将数据拉取到电脑上使用YUVViewer
这个软件查看是否真正转换成功。
参考
https://juejin.cn/post/7025223104569802789
专栏系列
Opengl ES之EGL环境搭建Opengl ES之着色器Opengl ES之三角形绘制Opengl ES之四边形绘制Opengl ES之纹理贴图Opengl ES之VBO和VAOOpengl ES之EBOOpengl ES之FBOOpengl ES之PBOOpengl ES之YUV数据渲染YUV转RGB的一些理论知识
关注我,一起进步,人生不止coding!!!
-
快看点丨A Representation Learning Framework for Property Graphs-KDD19
一、摘要图上的表示学习,也称为图嵌入,已经证明了它对一系列机器学习应用程序的重大影响,如分类、预...
来源: Opengl ES之RGB转NV21
快看点丨A Representation Learning Framework for Property Graphs-KDD19
非常流行的vue库,看这一篇就够了
理想车主夜间驾车中控显示有人追车?官方回应:视觉感知算法Bug
今日精选:一驾校打广告称不识字也能考驾照:3次考不过退费
55%~100%五种浓度:怡浓纯黑巧克力35元起400g大促
环球通讯!开电动车返乡要三思 网友晒高速服务区充电现状:已排成长队
联想小新Pro超能本2023升级140W快充:半小时就可回血66%
算法学习笔记(10): BSGS算法及其扩展算法
前沿资讯!2023年了 游戏还在背锅
第一款8K显示器发售6年:居然没人接班了
天天快资讯:丈夫称老家有别墅 女子回村直呼上当:网友调侃诚不欺我 真纯天然
被中国游客狠狠抛弃:韩国人口连续三年减少 女多男少拉不动内需
环球播报:便宜了2.5元 2023年春节档电影票价格7年来首次下降:你愿意去看吗?
合资家轿之王!新款日产轩逸e-POWER曝光:百公里仅需4升油
今热点:一箭14星成功升空 卫星首图回传:路面汽车清晰可见
AtCoder Beginner Contest 285 解题报告
新一代云原生日志架构 - Loggie的设计与实践
皮俑是什么做成的?皮俑为什么救吴邪?
擅长画虎的画家是?擅长画虎的十大画家
神奇宝贝的观看顺序是什么?神奇宝贝的大结局是什么?
用上半固态电池!赛力斯发布海外新车型SERES 5:订单超2万
国产骄傲!中国年度十大畅销新能源车型:外资仅有特斯拉上榜
每日播报!游客滑雪失控致女生被撞倒后抽搐 医生:危险数极高、常见骨折
拿老款忽悠当新款卖 女车主退车被日系4S店老板辱骂
风调雨顺的意思是什么?风调雨顺的下联是什么?
小年为什么分南方和北方?小年为什么吃麻糖?
液晶显示器故障怎么解决?液晶显示器故障维修大全
qq空间打不开是什么原因?qq空间打不开怎么办?
cf怎么鬼跳没声音?cf鬼跳教程按键手法
土豆网怎么下载?土豆网怎么没有了?
环球热讯:1.PyQt5【窗口组件】小部件-QWidgt
阿里云邮箱怎么注册?阿里云邮箱怎么发送邮件?
女粉丝入手小米13 曾是十年资深果粉:被小米和雷军打动
当前滚动:二手车商要“气晕”!美国已有特斯拉新车价格低于二手车
低调的江西 盛产新能源富豪
2023央视春晚主持人阵容公布:首次迎来双90后
每日头条!衡山雾凇火了 多名游客滑冰下山很危险 景区调整开放时间
Ansible 学习笔记 - 批量巡检站点 URL 状态
世界观速讯丨node和npm如何升级版本
世界信息:把车变成船 比亚迪真会玩
天天报道:《中国奇谭》值得这么夸吗?
【世界播资讯】首搭麒麟电池 全球MPV续航最长!极氪009开启交付
三星Galaxy S23系列售价泄露:12GB+1TB版本超1万元
行驶1900多米!祝融号已在火星留下近4000个“中”字
C#代码整洁之道读后总结与感想
【快播报】私家车与油罐车高速并排堵路 货车司机看不下去 主动让路
环球消息!男子开车时被激光笔照射 瞬间眼前一片黑!专家称可能会永久损伤
世界观点:小编要失业了 美国科技媒体CNET用AI写文章:读者完全没发现
天天快看点丨马斯克回应车主维权:涨价也没人给我补差价 就这吧
视焦点讯!太傲慢!微信更新内容仅“九个字” 网友:怕大家知道更新了什么吗?
全球速看:油车也有续航焦虑 高速服务区爆满:排队一小时 加油限量100元
消息!小米上架抗原试剂盒:19.9元5个 现货供应
《三体》电视剧开播 广受好评 网友:没看过原著的也被深深吸引
世界最新:2023春节档预售票房破亿:《流浪地球2》排片率第一 吴京/刘德华主演
世界今日报丨Recyclerview列表视频自动播放方案
观天下!QSAN A Quantum-probability based Signed Attention Network for Explainable Fa
剧版《三体》高热开播:收视率蹿升至第一、破腾讯视频记录
尼泊尔客机坠毁:机上72人不幸全部遇难
AMD显卡的尴尬!一个月了 两大品牌仍然没有RX 7900
世界新动态:中国首次全尺寸超导航行试验成功!时速50公里、冲击1000公里
全球信息:AJAX使用记录
环球热头条丨糖醋排骨里竟然藏着"量子点"!它咋这么厉害?
天天观天下!2个大厂 100亿级 超大流量 红包 架构方案
世界视点!CodeQL练习1
实时:day02-Spring基本介绍02
【天天速看料】Redis——数据类型
每日焦点!小朋友把山东的雪带回福建:半路化了 崩溃大哭
天天日报丨尼泊尔客机坠毁遇难人数升至68人:没有中国公民
环球快资讯:golang 为图片加水印
mysql--时间
环球焦点!王思聪打人后行政拘留为什么能暂缓执行?罗翔科普
天天观速讯丨AMD悄悄公布31个CPU漏洞:4个极危险、Zen4高枕无忧
今日热文:Nginx面试题(史上最全 + 持续更新)
当前快讯:Atcoder Regular Contest ARC 153 A B C D 题解
焦点关注:PhotoEnhancer人工智能一键修复老照片,老照片修复,图像去噪
男子花32万买比亚迪海豹 内心崩溃:汽配城都没这么难看
焦点要闻:节能版酷睿i9-13900T现身:35W战平12900K
观天下!腾讯开出48人惩治名单 马化腾曾称内部贪腐“触目惊心”
Phi的反函数
【环球热闻】110度高烧不退!AMD RX 7900 XTX退换货率高达11%
为啥北方二十三过小年 南方却是二十四?和康熙乾隆有关
观察:无广告一年免费用!通信UOS家庭版22.0开始推送
111111
男子买898元零食P图付款 被抓现行:实际支付了1分钱
天天通讯!基于传奇车型AE86!丰田推出两款新能源概念改装车
CF构造题1600-1800(2)
女子骑电动车载两人闯红灯被撞 被判全责 网友:这才是公正
苹果回应iPhone车祸监测误报频发:正收集相关反馈
《新·福音战士剧场版:终》国内海报抄袭!竹也文化官方布道歉声明
Python开发的常用组件
每日观察!推荐一本正在看的书
环球今热点:几十年数学难题被谷歌研究员意外突破 当年差点被导师赶出门
B站2022百大UP主出炉:手工耿入选 走向世界的手工匠人
天天亮点!稳居春节档票房前三:《流浪地球2》官方揭秘太空电梯创作思路
世界讯息:12月新能源销量排名出炉:比亚迪吉利长安强攻 特斯拉扛不住了?
【全球独家】读编程与类型系统笔记08_面向对象变成的元素
观速讯丨长征第462发!我国成功发射一箭14星:“共享”火箭了解下
国内《新·福音战士剧场版:终》限定海报被指抄袭 官方正在联系画师确认
无法恢复!微软杀软Defender误删开始菜单/任务栏捷方式