35.Android Studio下FFmpeg的編譯和使用(九.OpenGLES播放視頻)

項目源碼
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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章