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);
}
相关文章
|
存储 编解码 Linux
FFmpeg+SDL播放器开发实践:解析、解码、渲染全流程详解
FFmpeg+SDL播放器开发实践:解析、解码、渲染全流程详解
|
16天前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
78 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
6月前
|
设计模式 存储 缓存
【ffmpeg C++ 播放器优化实战】优化你的视频播放器:使用策略模式和单例模式进行视频优化
【ffmpeg C++ 播放器优化实战】优化你的视频播放器:使用策略模式和单例模式进行视频优化
201 0
|
6月前
|
人机交互 C++
QT + FFmpeg 5.x + x264 + x265 + SDL2 音视频播放器
QT + FFmpeg 5.x + x264 + x265 + SDL2 音视频播放器
214 0
|
24天前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
42 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
29天前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
115 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
1月前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
58 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势