开发者社区> 黑夜路口> 正文

35.FFmpeg+OpenGLES+OpenSLES播放器实现(九.OpenGLES播放视频)

简介: 项目源码OpenGL ES 2.0 中文文档 完整代码,一切尽在注释中 extern "C" JNIEXPORT void JNICALL Java_com_rzm_ffmpegplayer_FFmpegPlayer_initOpenGL(JNIEnv.
+关注继续查看

项目源码
OpenGL ES 2.0 中文文档

完整代码,一切尽在注释中

extern "C"
JNIEXPORT void JNICALL
Java_com_rzm_ffmpegplayer_FFmpegPlayer_initOpenGL(JNIEnv *env, jobject instance, jstring url_,
                                                  jobject surface) {
    const char *url = env->GetStringUTFChars(url_, 0);

    FILE *fp = fopen(url, "rb");
    if (!fp) {
        LOGE("open file %s failed!", url);
        return;
    }

    /**************************EGL初始化********************************************/

    //1.创建渲染窗口
    ANativeWindow *aNativeWindow = ANativeWindow_fromSurface(env, surface);

    //2.EGL Display创建
    //EGL提供了平台无关类型EGLDisplay表示窗口。定义EGLNativeDisplayType是为了匹配原生窗口系统的显示类型,
    // 对于Windows,EGLNativeDisplayType被定义为HDC,
    // 对于Linux系统,被定义为Display*类型,
    // 对于Android系统,定义为ANativeWindow *类型,
    // 为了方便的将代码转移到不同的操作系统上,应该传入EGL_DEFAULT_DISPLAY,返回与默认原生窗口的连接。
    // 如果连接不可用,则返回EGL_NO_DISPLAY
    EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (eglDisplay == EGL_NO_DISPLAY) {
        LOGE("eglGetDisplay failed%d", eglGetError());
        return;
    }

    //3.初始化Display
    //创建与本地原生窗口的连接后需要初始化EGL,使用函数eglInitialize进行初始化操作。如果 EGL 不能初始化,
    // 它将返回EGL_FALSE,并将EGL错误码设置为EGL_BAD_DISPLAY表示制定了不合法的EGLDisplay,
    // 或者EGL_NOT_INITIALIZED表示EGL不能初始化。使用函数eglGetError用来获取最近一次调用EGL函数出错的错误代码

    //参数
    //EGLDisplay display 创建的EGL连接
    //EGLint *majorVersion 返回EGL主板版本号
    //EGLint *minorVersion 返回EGL次版本号
    if (EGL_TRUE != eglInitialize(eglDisplay, 0, 0)) {
        LOGE("eglInitialize failed");
        return;
    }

    //3. surface窗口配置
    //窗口配置有两种方式:一种方式是使用eglGetConfigs函数获取底层窗口系统支持的所有EGL表面配置(Config),然后再使用
    // eglGetConfigAttrib依次查询每个EGLConfig相关的信息,Config有众多的Attribute,这些Attribute决定FrameBuffer
    // 的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改。EGLConfig包含了渲染表面的所有信息,包括可用颜色、
    // 缓冲区等其他特性。
    // 另一种方式是指定我们需要的渲染表面配置,让EGL自己选择一个符合条件的EGLConfig配置。eglChooseChofig
    // 调用成功返回EGL_TRUE,失败时返回EGL_FALSE,如果attribList包含了未定义的EGL属性,或者属性值不合法,
    // EGL代码被设置为EGL_BAD_ATTRIBUTR

    //Config实际就是FrameBuffer的参数
    EGLConfig eglConfig;

    //attribList参数在EGL函数中可以为null或者指向一组以EGL_NONE结尾的键对值
    //通常以id,value依次存放,对于个别标识性的属性可以只有id,没有value
    EGLint configAttr[] = {
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_SURFACE_TYPE,
            EGL_WINDOW_BIT,
            EGL_NONE
    };
    EGLint eglConfigNum;

    //参数
    //EGLDisplay dpy 创建的和本地窗口系统的连接
    //const EGLint *attrib_list, 指定渲染表面的参数列表,可以为null
    //EGLConfig *configs  调用成功,返会符合条件的EGLConfig列表
    //EGLint config_size, 最多返回的符合条件的EGLConfig个数
    //EGLint *num_config 实际返回的符合条件的EGLConfig个数
    if (EGL_TRUE != eglChooseConfig(eglDisplay, configAttr, &eglConfig, 1, &eglConfigNum)) {
        LOGE("eglChooseConfig failed");
        return;
    }

    //4. 创建surface

    // 有了符合条件的EGLConfig后,就可以通过eglCreateWindowSurface函数创建渲染表面。使用这个函数的前提是要使
    // 用原生窗口系统提供的API创建一个窗口。eglCreateWindowSurface中attribList一般可以使用null即可。
    // 函数调用失败会返回EGL_NO_SURFACE,并设置对应的错误码

    //使用eglCreateWindowSurface函数创建在窗口上的渲染表面,此外还可以使用eglCreatePbufferSurface创建
    // 屏幕外渲染表面(Pixel Buffer 像素缓冲区)。使用Pbuffer一般用于生成纹理贴图,不过该功能已经被
    // FrameBuffer替代了,使用帧缓冲对象的好处是所有的操作都由OpenGL ES来控制。使用Pbuffer的方法和前面创建
    // 窗口渲染表面一样,需要改动的地方是在选取EGLConfig时,增加EGL_SURFACE_TYPE参数使其值包含
    // EGL_PBUFFER_BIT。而该参数默认值为EGL_WINDOW_BIT。
    //EGLSurface eglCreatePbufferSurface( EGLDisplay display, EGLConfig config, EGLint const * attrib_list // 指定像素缓冲区属性列表 );

    //Surface实际上就是一个FrameBuffer
    EGLSurface eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, aNativeWindow, 0);
    if (eglSurface == EGL_NO_SURFACE) {
        LOGE("eglCreateWindowSurface failed");
        return;
    }

    //4 创建关联的上下文

    //使用eglCreateContext为当前的渲染API创建EGL渲染上下文,返回一个上下文,当前的渲染API是由函数eglBindAPI
    // 设置的。OpenGL ES是一个状态机,用一系列变量描述OpenGL ES当前的状态如何运行,我们通常使用如下途径去更改
    // OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。比如我想告诉OpenGL ES接下来要绘制
    // 三角形,可以通过一些上下文变量来改变OpenGL ES的状态,一旦改变了OpenGL ES的状态为绘制三角形,下一个命令
    // 就会画出三角形。通过这些状态设置函数就会改变上下文,接下来的操作总会根据当前上下文的状态来执行,除非再次
    // 重新改变状态。

    const EGLint ctxAttr[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
    };
    EGLContext eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, ctxAttr);
    if (eglContext == EGL_NO_CONTEXT) {
        LOGE("eglCreateContext failed!");
        return;
    }

    //5.指定某个EGLContext为当前上下文。使用eglMakeCurrent函数进行当前上下文的绑定。一个程序可能创建多个EGLContext,
    // 所以需要关联特定的EGLContext和渲染表面,一般情况下两个EGLSurface参数设置成一样的。
    if (EGL_TRUE != eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
        LOGE("eglMakeCurrent failed!");
        return;
    }

    LOGI("EGL Init Success!");

    /**************************EGL初始化********************************************/

    /**************************shader初始化********************************************/
    //定点shader初始化
    GLint vshader = initShader(vertexShader, GL_VERTEX_SHADER);

    //片元yuv420 shader初始化
    GLint fshader = initShader(fragYUV420P, GL_FRAGMENT_SHADER);
    /**************************shader初始化********************************************/

    /**************************渲染程序初始化********************************************/
    //7.使用OpenGL相关的API进行绘制操作。.....

    //创建渲染程序
    GLint program = glCreateProgram();
    if (program == 0) {
        LOGE("glCreateProgram failed!");
        return;
    }
    //渲染程序中加入着色器代码
    glAttachShader(program, vshader);
    glAttachShader(program, fshader);

    //链接程序
    glLinkProgram(program);
    GLint status = 0;
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    if (status != GL_TRUE) {
        LOGE("glLinkProgram failed!");
        return;
    }
    glUseProgram(program);
    LOGI("glLinkProgram success!");
    /////////////////////////////////////////////////////////////


    //加入三维顶点数据 两个三角形组成正方形
    //顶点坐标系描述了GopenGL的绘制范围,他以绘制中心为原点,在2D图形下,左边界为到x -1,右边界到x 1,上边界到y 1
    //下边界到y -1,3D下同样道理。定点坐标系就是OpenGL的绘制区间
    static float vers[] = {
            1.0f, -1.0f, 0.0f,  //右下
            -1.0f, -1.0f, 0.0f, //左下
            1.0f, 1.0f, 0.0f,   //右上
            -1.0f, 1.0f, 0.0f,  //左上
    };
    GLuint apos = (GLuint) glGetAttribLocation(program, "aPosition");
    glEnableVertexAttribArray(apos);
    //传递顶点 取3个数据,跳转12个字节位(3个数据)再取另外3个数据,这是实现块状数据存储的关键,很多函数里都有这个参数,通常写作int stride
    glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 12, vers);

    //加入纹理坐标数据
    //纹理坐标的坐标系以纹理左下角为坐标原点,向右为x正轴方向,向上为y轴正轴方向。他的总长度是1。即纹理图片的
    // 四个角的坐标分别是:(0,0)、(1,0)、(0,1)、(1,1),分别对应左下、右下、左上、右上四个顶点。
    static float txts[] = {
            1.0f, 0.0f, //右下
            0.0f, 0.0f, //左下
            1.0f, 1.0f, //右上
            0.0, 1.0    //左上
    };
    GLuint atex = (GLuint) glGetAttribLocation(program, "aTexCoord");
    glEnableVertexAttribArray(atex);
    glVertexAttribPointer(atex, 2, GL_FLOAT, GL_FALSE, 8, txts);

    LOGI("glVertexAttribPointer success");
    /**************************渲染程序传递数据********************************************/

    /**************************纹理设置********************************************/
    int width = 176;
    int height = 144;

    width = 352;
    height = 288;
    //材质纹理初始化
    //设置纹理层

    //对于纹理第1层
    glUniform1i(glGetUniformLocation(program, "yTexture"), 0);
    //对于纹理第2层
    glUniform1i(glGetUniformLocation(program, "uTexture"), 1);
    //对于纹理第3层
    glUniform1i(glGetUniformLocation(program, "vTexture"), 2);

    //创建opengl纹理
    GLuint texts[3] = {0};
    //创建三个纹理对象
    //在纹理资源使用完毕后(一般是程序退出或场景转换时),一定要删除纹理对象,释放资源。
    //glDeleteTextures(Count:Integer;TexObj:Pointer);
    glGenTextures(3, texts);



    //使用glBindTexture将创建的纹理绑定到当前纹理。这样所有的纹理函数都将针对当前纹理。
    glBindTexture(GL_TEXTURE_2D, texts[0]);
    //设置缩小滤镜
    /**
     * 第一个参数表明是针对何种纹理进行设置
     * 第二个参数表示要设置放大滤镜还是缩小滤镜
     *
     * 在纹理映射的过程中,如果图元的大小不等于纹理的大小,OpenGL便会对纹理进行缩放以适应图元的尺寸。
     * 我们可以通过设置纹理滤镜来决定OpenGL对某个纹理采用的放大、缩小的算法。
     *
     * 第三个参数表示使用的滤镜
     *
     * 第三个参数可选项如下:
     *
     * GL_NEAREST   取最邻近像素
     * GL_LINEAR    线性内部插值
     * GL_NEAREST_MIPMAP_NEAREST    最近多贴图等级的最邻近像素
     * GL_NEAREST_MIPMAP_LINEAR     在最近多贴图等级的内部线性插值
     * GL_LINEAR_MIPMAP_NEAREST     在最近多贴图等级的外部线性插值
     * GL_LINEAR_MIPMAP_LINEAR  在最近多贴图等级的外部和内部线性插值
     *
     */

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    //设置放大滤镜
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //glTexImage2D函数将Pixels数组中的像素值传给当前绑定的纹理对象,于是便创建了纹理,Pixels是最后一个参数
    glTexImage2D(
            //纹理的类型
            GL_TEXTURE_2D,
            //纹理的等级 0默认 级的分辨率最大
            0,
            //gpu内部格式 亮度,灰度图
            GL_LUMINANCE,
            //纹理图像的宽度和高度 拉升到全屏
            width, height,
            //边框大小
            0,
            //像素数据的格式 亮度,灰度图 要与上面一致
            GL_LUMINANCE,
            //像素值的数据类型
            GL_UNSIGNED_BYTE,
            //纹理的数据(像素数据)
            NULL
    );

    //使用glBindTexture将创建的纹理绑定到当前纹理。这样所有的纹理函数都将针对当前纹理。
    glBindTexture(GL_TEXTURE_2D, texts[1]);
    //调用glTexParameter来设置纹理滤镜
    //设置缩小滤镜
    /**
     * 第一个参数表明是针对何种纹理进行设置
     * 第二个参数表示要设置放大滤镜还是缩小滤镜
     * 第三个参数表示使用的滤镜
     *
     * 第三个参数可选项如下:
     *
     * GL_NEAREST   取最邻近像素
     * GL_LINEAR    线性内部插值
     * GL_NEAREST_MIPMAP_NEAREST    最近多贴图等级的最邻近像素
     * GL_NEAREST_MIPMAP_LINEAR     在最近多贴图等级的内部线性插值
     * GL_LINEAR_MIPMAP_NEAREST     在最近多贴图等级的外部线性插值
     * GL_LINEAR_MIPMAP_LINEAR  在最近多贴图等级的外部和内部线性插值
     *
     * 多贴图纹理(Mip Mapping)为一个纹理对象生成不同尺寸的图像。在需要时,根据绘制图形的大小来决定采用的纹理
     * 等级或者在不同的纹理等级之间进行线性内插。使用多贴图纹理的好处在于消除纹理躁动。这种情况在所绘制的景物
     * 离观察者较远时常常发生(如图6.6-1和6.6-2)。由于多贴图纹理现在的渲染速度已经很快,以至于和普通纹理没有
     * 什么区别,我们现在一般都使用多贴图纹理。
     */
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    //设置放大滤镜
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //glTexImage2D函数将Pixels数组中的像素值传给当前绑定的纹理对象,于是便创建了纹理,Pixels是最后一个参数
    glTexImage2D(
            //纹理的类型
            GL_TEXTURE_2D,
            //纹理的等级 0默认 级的分辨率最大
            0,
            //gpu内部格式 亮度,灰度图
            GL_LUMINANCE,
            //纹理图像的宽度和高度 拉升到全屏
            width, height,
            //边框大小
            0,
            //像素数据的格式 亮度,灰度图 要与上面一致
            GL_LUMINANCE,
            //像素值的数据类型
            GL_UNSIGNED_BYTE,
            //纹理的数据(像素数据)
            NULL
    );

    //使用glBindTexture将创建的纹理绑定到当前纹理。这样所有的纹理函数都将针对当前纹理。
    glBindTexture(GL_TEXTURE_2D, texts[2]);
    //缩小的过滤器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //glTexImage2D函数将Pixels数组中的像素值传给当前绑定的纹理对象,于是便创建了纹理,Pixels是最后一个参数
    glTexImage2D(
            //纹理的类型
            GL_TEXTURE_2D,
            //纹理的等级 0默认 级的分辨率最大
            0,
            //gpu内部格式 亮度,灰度图
            GL_LUMINANCE,
            //纹理图像的宽度和高度 拉升到全屏
            width, height,
            //边框大小
            0,
            //像素数据的格式 亮度,灰度图 要与上面一致
            GL_LUMINANCE,
            //像素值的数据类型
            GL_UNSIGNED_BYTE,
            //纹理的数据(像素数据)
            NULL
    );
    LOGI("glTexImage2D success");


    /**************************纹理设置********************************************/
    unsigned char *buf[3] = {0};
    buf[0] = new unsigned char[width * height];
    buf[1] = new unsigned char[width * height / 4];
    buf[2] = new unsigned char[width * height / 4];

    for (int i = 0; i < 10000; i++) {
        //memset(buf[0],i,width*height);
        // memset(buf[1],i,width*height/4);
        //memset(buf[2],i,width*height/4);

        //420p   yyyyyyyy uu vv
        if (feof(fp) == 0) {
            //yyyyyyyy
            fread(buf[0], 1, width * height, fp);
            fread(buf[1], 1, width * height / 4, fp);
            fread(buf[2], 1, width * height / 4, fp);
        }




        //激活第1层纹理,绑定到创建的opengl纹理
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texts[0]);
        //替换纹理内容
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE,
                        buf[0]);


        //激活第2层纹理,绑定到创建的opengl纹理
        glActiveTexture(GL_TEXTURE0 + 1);
        glBindTexture(GL_TEXTURE_2D, texts[1]);
        //替换纹理内容
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_LUMINANCE,
                        GL_UNSIGNED_BYTE, buf[1]);

        //激活第2层纹理,绑定到创建的opengl纹理
        glActiveTexture(GL_TEXTURE0 + 2);
        glBindTexture(GL_TEXTURE_2D, texts[2]);
        //替换纹理内容
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_LUMINANCE,
                        GL_UNSIGNED_BYTE, buf[2]);



        //三维绘制
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        //窗口显示
        eglSwapBuffers(eglDisplay, eglSurface);

    }
    /**************************纹理显示********************************************/
    LOGI("eglSwapBuffers success");
    env->ReleaseStringUTFChars(url_, url);
}

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【Android 高性能音频】Oboe 函数库简介 ( Oboe 简介 | Oboe 特点 | Oboe 编译工具 | Oboe 相关文档 | Oboe 测试工具 )
【Android 高性能音频】Oboe 函数库简介 ( Oboe 简介 | Oboe 特点 | Oboe 编译工具 | Oboe 相关文档 | Oboe 测试工具 )
263 0
FFmpeg 开发(05):FFmpeg + OpenGLES 实现视频解码播放和视
前面 Android FFmpeg 开发系列文章中,我们已经利用 FFmpeg 的解码功能和 ANativeWindow 的渲染功能,实现了的视频的解码播放。但是,当你想为播放器做一些视频滤镜时,如加水印、旋转缩放等效果,使用 OpenGL ES 实现起来就极为方便。
604 0
FFmpeg 开发(07):FFmpeg + OpenGLES 实现 3D 全景播放器
前文中,我们已经利用 FFmpeg + OpenGLES + OpenSLES 实现了一个多媒体播放器,本文将基于此播放器实现一个酷炫的 3D 全景播放器。
367 0
FFmpeg 开发(04):FFmpeg + OpenGLES 实现音频可视化播放
本文基于上一篇文章 FFmpeg + OpenSLES 实现音频解码播放 ,利用 FFmpeg 对一个 Mp4 文件的音频流进行解码,然后将解码后的 PCM 音频数据进行重采样,最后利用 OpenSLES 进行播放的同时,将 PCM 音频一个通道的数据实时渲染成柱状图。
240 0
FFmpeg 开发(03):FFmpeg + OpenSLES 实现音频解码播放
本文将利用 FFmpeg 对一个 Mp4 文件的音频流进行解码,然后使用 libswresample 将解码后的 PCM 音频数据转换为目标格式的数据,最后利用 OpenSLES 进行播放。
389 0
34.FFmpeg+OpenGLES+OpenSLES播放器实现(八.OpenSLES播放音频)
项目源码OpenSL ES 文档 OpenSLES:(Open Sound Library for Embedded Systems) OpenSLES是跨平台、针对嵌入式系统精心优化的硬件音频加速API。
1571 0
31.FFmpeg+OpenGLES+OpenSLES播放器实现(五.FFmpeg解封装)
项目源码FFmpeg开发文档 Android Studio的开发环境已经准备好,接下来开始正式的写ndk代码,首先创建一个FFmpeg工具类,添加native方法 import android.
1275 0
32.FFmpeg+OpenGLES+OpenSLES播放器实现(六.FFmpeg音视频解码)
项目源码FFmpeg开发文档 解码分为软解码和硬解码,那么什么是软解码和硬解码,二者有什么区别?简单来说,在于是否使用CPU进行解码,最初视频解码都是通过CPU进行的,那时候视频分辨率较低,CPU完全可以胜任解码的工作,但是随着高清视频的出现,使用CPU进行解码的压力越来越大 软解码 使用CPU进行解码,所以就很容易造成CPU负载过大。
2606 0
33.FFmpeg+OpenGLES+OpenSLES播放器实现(七.FFmpeg像素格式转换和音频重采样)
项目源码FFmpeg开发文档 像素格式转换 像素格式描述了像素数据存储所用的格式。定义了像素在内存中的编码方式。FFmpeg支持的像素格式主要是rbg和yuv两种,具体可以在结构体AVPixelFormat中看到 /** * Pixel format.
1807 0
28.FFmpeg+OpenGLES+OpenSLES播放器实现(二.Ubunto系统环境配置)
项目源码FFmpeg开发文档 编译过程中涉及到很多ndk中的so库和头文件以及交叉编译的工具,在命令执行的时候会在ndk相应的目录下去查找,所以我们可以使用export命令事先将这些路径设置到环境变量,使用的时候可以很方便的找到 //NDK加入环境变量.
1547 0
+关注
黑夜路口
安卓高级工程师,目前任职于Wifi万能钥匙
文章
问答
视频
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载