Opengl ES之RGB转NV21

news2024/11/27 18:33:34

前言

在上一篇理论文章中我们介绍了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 + 16
Cb = -0.148R - 0.291G + 0.439B + 128
Cr =  0.439R - 0.368G - 0.071B + 128

RGB 转 BT.709 YUV

Y  =  0.183R + 0.614G + 0.062B + 16
Cb = -0.101R - 0.339G + 0.439B + 128
Cr =  0.439R - 0.399G - 0.040B + 128

或者也可以使用矩阵运算的方式进行转换,更加的便捷:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0cSXuTX-1673850949022)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/RGB%E8%BD%ACYUV%E5%85%AC%E5%BC%8F.png)]

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次偏移采样。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-awPI6kPS-1673850949023)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/y%E5%81%8F%E7%A7%BB%E9%87%87%E6%A0%B7.png)]

同理,生成对于UV数据也需要进行3次额外的偏移采样。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v96gEiz5-1673850949023)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/UV%E5%81%8F%E7%A7%BB%E9%87%87%E6%A0%B7.png)]

在着色器中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和VAO
Opengl ES之EBO
Opengl ES之FBO
Opengl ES之PBO
Opengl ES之YUV数据渲染
YUV转RGB的一些理论知识

关注我,一起进步,人生不止coding!!!
微信扫码关注

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

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

相关文章

SSL协议未开启是什么意思?

SSL协议未开启是指服务器中的服务没有开启或者没有SSL模块。在互联网中&#xff0c;数据交互传输基于http明文协议&#xff0c;随着互联网的不断发展&#xff0c;http协议展现出它的缺陷&#xff0c;通过http协议传输的数据容易被攻击者窃取、篡改或仿冒。为适应新形势下的网络…

【C语言进阶】文件操作详解

文章目录一.文件指针1.什么是文件指针2.如何使用文件指针二.文件操作函数1.流与文件2.顺序读写函数三.文本文件和二进制文件四.文件缓冲区一.文件指针 1.什么是文件指针 文件指针其实是文件类型指针的简称&#xff0c;我们常常会使用文件保存数据&#xff0c;而每个被使用的文…

SMILEtrack:基于相似度学习的多目标跟踪

文章目录摘要1、简介2、相关工作2.1、Tracking-by-Detection2.1.1、检测方法2.1.2、数据关联方法2.2、Tracking-by-Attention3、方法3.1、体系结构概述3.2.1、图像切片注意(ISA)块3.2.3、Q-K-V注意力块3.3、基于相似匹配级联(SMC)的目标跟踪算法4、实验结果4.1、数据集4.2、MOT…

XMind导入Markdown(利用Typora导出opml)

安装Xmind XMind 是一款非常实用的商业思维导图软件 首先&#xff0c;安装Xmind并打开。通过"帮助"——>“关于Xmind”&#xff0c;可以获取到当前的版本号为 XMind 8 Update 9 在"文件"——>“导入”&#xff0c;可以看到Xmind支持的导入格式仅有…

get请求和post请求

get请求 1.get请求的特点 通过一个URL来访问一个地址&#xff0c;就比如说127.0.0.1:7001,这种请求方式就是get请求&#xff0c;get请求可以直接在URL中添加参数&#xff0c;通过URL来传递参数。 优点&#xff1a;使用简单&#xff0c;清晰有条理&#xff0c;比较适合网站和…

十五天学会Autodesk Inventor,看完这一系列就够了(三),拉伸命令

众所周知&#xff0c;Autocad是一款用于二维绘图、详细绘制、设计文档和基本三维设计&#xff0c;现已经成为国际上广为流行的绘图工具。Autodesk Inventor软件也是美国AutoDesk公司推出的三维可视化实体模拟软件。因为很多人都熟悉Autocad&#xff0c;所以再学习Inventor&…

盘点2022年度A站UE神作top

A站大家都应该很熟悉了&#xff0c;在全球的CG行业都是属于专业化十分高的网站&#xff0c;平台内展示的内容包括影视、动画、娱乐等等板块&#xff0c;更是收录了众多大神艺术家的作品&#xff0c;多看看可以最直接的了解整个行业的审美趋势与技术动向。正好最近2022年A站人气…

数据结构:线性表的链式表示和实现

顺序表仅适用于不常进行插人和删除的线性表。因为在顺序存储表示的线性表中插入或删除一个数据元素,平均约需移动表中一半元素,这个缺陷是由于顺序存储要求线性表的元素依次“紧挨”存放造成的。因此对于经常需要进行插人和删除操作的线性表,就需要选择其他的存储表示方法。现在…

C进阶:文件的基础操作

本文主要讲解文件基础操作的知识。 目录 一.文件指针 二.文件的打开和关闭 1.文件的打开 打开文件我们需要使用到 fopen 函数&#xff1b; 注意&#xff08;文件扩展名&#xff09;&#xff1a; 2.文件的关闭 三.文件的读写 1.读与写&#xff0c;输出与输入的概念 ​编…

VueUse(中文)——简介

一、VueUse——简介 VueUse是由Anthony Fu等大佬写的基于Vue的自定义钩子集合。类似于基于React的 ahooks功能丰富&#xff1a;200功能无缝迁移&#xff1a;适用于Vue 3和Vue2.7版本之后支持tree shaking&#xff1a;只引入自己需要的那部分&#xff0c;打包会更小还有其他的亮…

Chrome V3版开发教程使用Vue 3.x+Ant构建项目

简介 ​ Google在2023年将会遗弃V2版本&#xff0c;而目前在CSDN平台上的大部分Chrome插件的开发教程都是基于V2版本的&#xff0c;V3版本和V2的版本上还是有很大的区别&#xff0c;就比如manifest.json中的写法差距就很大&#xff0c;所以对于即将要开发Chrome插件的小伙伴来…

机器学习的相关概念与建模流程

文章目录一、机器学习简介1. 机器学习的相关定义2. 一次简单的机器学习任务&#xff1a;鸢尾花分类二、数据与数据集相关概念1. 数据与数据集2. 特征与标签3. 连续变量和离散变量4. 模型类型三、机器学习建模一般流程1. 提出基本模型2. 确定损失函数3. 根据损失函数性质&#x…

Servlet —— Smart Tomcat,以及一些访问出错可能的原因

JavaEE传送门JavaEE HTTPS —— HTTPS的加密方式 Servlet —— Tomcat, 初学 Servlet 程序 目录Smart TomcatSmart Tomcat 的下载配置 Smart Tomcat 插件访问出错404405500无法访问此网站Smart Tomcat 在上一篇文章中, 我们手动拷贝 war 包到 Tomcat 中的过程比较麻烦, 我们…

解析 json,整理分散数据入库

【问题】在 json 文件中有以下内容&#xff1a;现在需要将 json 中分散的数据整理入库。“LIST”中包含多个子孙记录&#xff0c;主要目标是”GROUPNAME”、”SERVICES”。“GROUPNAME”值、”SERVICES”中的键编号对应了数据库表 _groups 的 Groupname 和 ID。“SERVICES”每个…

PicGo+Gitee+Typora实现markdown图床

PicGoGitee实现markdown图床 情景概要 写博客的时候&#xff0c;总是需要插入图片的&#xff0c;图片存在本地的话上传到博客网站去就没法显示了 就算一个图一个图的复制粘贴上去&#xff0c;想移植到其他的博客网站&#xff0c;图就会失效&#xff0c;我们就需要图床 图床 …

系分 - 案例分析 - 项目管理

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录系分 - 案例分析 - 项目管理时间管理进度管理典型例题题目描述参考答案系分 - 案例分析 - 项目管理 时间管理 进度管理 ① 项目进度管理包括为管理项目按时完成所需的7个过程&#xff0c;具体为&#xf…

MyBatis-Plus数据安全保护(字段脱敏)

项目创建POM依赖 <dependency><!--MyBatis-Plus 企业级模块--><groupId>com.baomidou</groupId><artifactId>mybatis-mate-starter</artifactId><version>1.2.8</version> </dependency> YML配置 spring:datasource:# 配…

熟悉Spring框架?Spring容器使用流程,注解 你真的会吗?(让你一下通透~)

目录 前言 一、ApplicationContext 和 BeanFactory 的区别 二、主要工作流程 三、开发步骤 3.1、创建Maven项目 3.2、创建Spring的核心配置文件 3.3、Spring注解 3.3.1、类注解 3.3.2、五大类注解的关系 3.3.3、方法注解 3.3.4、属性注入 3.3.5、五大注解关系图&am…

【03】FreeRTOS的任务创建(静态和动态)和删除

目录 1.任务创建和删除的API函数 1.1动态创建任务函数 1.1.1实现动态创建任务流程 1.1.2任务控制块结构成员介绍 1.2静态创建任务函数 1.2.1实现静态创建任务流程 1.3任务删除函数 1.3.1删除任务流程 2.任务创建和删除&#xff08;动态方法&#xff09; 2.1宏confi…

一文掌握项目估算工具及方法【管理有度13】

01为什么要做估算我们的业务方经常抱怨资源不足&#xff0c;团队需求的吞吐率太低&#xff0c;资源和需求量的不匹配是一个永恒的话题&#xff0c;解决方案应该不只是增加资源&#xff0c;增加了资源如果需求的输入量不能稳定保证&#xff0c;那资源就会处于持续浪费的状态当中…