Ffmpeg之native渲染YUV視頻

Android 平臺 不能直接渲染YUV格式視頻,需要做YUV 轉換 RGB,再去渲染,轉換的方式有兩種:

1、使用ffmpeg的轉換api,將解碼後的原數據轉換爲RGB格式數據,再渲染

2、使用opengl GPU 做轉換 渲染

本文章主要介紹第二種方式基於NDK來做渲染(使用GPU做渲染,可以提高效率)

使用GPU 來做渲染 就需要 接觸到 EGL 和 OPENGL 這兩個詞,這裏摘錄一些介紹和使用:

通俗上講,OpenGL是一個操作GPU的API,它通過驅動向GPU發送相關指令,控制圖形渲染管線狀態機的運行狀態。但OpenGL需要本地視窗系統進行交互,這就需要一箇中間控制層,最好與平臺無關。

EGL——因此被獨立的設計出來,它作爲OpenGL ES和本地窗口的橋樑。

EGL 是 OpenGL ES(嵌入式)和底層 Native 平臺視窗系統之間的接口。EGL API 是獨立於OpenGL ES各版本標準的獨立API,其主要作用是爲OpenGL指令創建 Context 、繪製目標Surface 、配置Framebuffer屬性、Swap提交繪製結果等。此外,EGL爲GPU廠商和OS窗口系統之間提供了一個標準配置接口。

一般來說,OpenGL ES 圖形管線的狀態被存儲於 EGL 管理的一個Context中。而Frame Buffers 和其他繪製 Surfaces 通過 EGL API進行創建、管理和銷燬。 EGL 同時也控制和提供了對設備顯示和可能的設備渲染配置的訪問。

EGL標準是C的,在Android系統Java層封裝了相關API。

摘錄自https://gameinstitute.qq.com/community/detail/103360

瞭解了EGL 和 OPENGL 就需要知道怎使用了,上面的連接已經 詳細介紹了,API的調用流程,這裏直接跳出代碼

int openEglYuv(ANativeWindow *window) {
    
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (display == EGL_NO_DISPLAY) {
        LOGE("YUV : get Display failure")
        return -1;
    }
    EGLint major;
    EGLint minor;
    EGLBoolean success = eglInitialize(display, &major, &minor);
    if (!success) {
        eglTerminate(display);
        LOGE("YUV : init Display failure")
        return -1;
    }
    global_Context.eglDisplay = display;
    EGLConfig config;
    EGLint configNum;
    EGLint configSpec[] = {
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
//            EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
            EGL_STENCIL_SIZE, 8,
            EGL_NONE
    };


    success = eglChooseConfig(display, configSpec, &config, 1, &configNum);
    if (!success) {
        eglTerminate(display);
        LOGE("YUV : ChooseConfig failure")
        return -1;
    }


    EGLSurface surface = eglCreateWindowSurface(display, config, window, 0);
    if (surface == EGL_NO_SURFACE) {
        LOGE("YUV : CreateWindowSurface failure")
        eglTerminate(display);
        return -1;
    }
    global_Context.eglSurface = surface;
    EGLSurface psurface = eglCreatePbufferSurface(display, config, 0);
    if (psurface == EGL_NO_SURFACE) {
        LOGE("YUV : CreateWindowSurface failure")
        eglTerminate(display);
        return -1;
    }
    global_Context.eglPsurface = psurface;


    EGLint ctxAttr[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
    };
    EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr);
    if (context == EGL_NO_CONTEXT) {
        eglDestroySurface(display, psurface);
        eglTerminate(display);
        LOGE("YUV : CreateContext failure")
        return -1;
    }

    global_Context.eglContext = context;


    return 0;

}

這一段代碼只是做了相關的初始化工作,現在來編寫 渲染相關的邏輯,這裏寫下步驟:

1、編寫 頂點,片元 着色器 字符串。

2、加載 頂點,片元着色器

3、創建  渲染工程,管理着色器

4、提取着色器 參數,初始化 紋理(配置渲染邏輯)

5、渲染YUV

頂點 ,片元 着色器的語法,沒怎麼接觸,不做詳細分析,這裏貼出代碼:

const char *vertexShader_ = GET_STR(
        attribute
        vec4 aPosition;//輸入的頂點座標,會在程序指定將數據輸入到該字段
        attribute
        vec2 aTextCoord;//輸入的紋理座標,會在程序指定將數據輸入到該字段
        varying
        vec2 vTextCoord;//輸出的紋理座標
        void main() {
            //這裏其實是將上下翻轉過來(因爲安卓圖片會自動上下翻轉,所以轉回來)
            vTextCoord = vec2(aTextCoord.x, 1.0 - aTextCoord.y);
            //直接把傳入的座標值作爲傳入渲染管線。gl_Position是OpenGL內置的
            gl_Position = aPosition;
        }
);

const char *fragYUV420P_ = GET_STR(
        precision
        mediump float;
        varying
        vec2 vTextCoord;
        //輸入的yuv三個紋理
        uniform
        sampler2D yTexture;//採樣器
        uniform
        sampler2D uTexture;//採樣器
        uniform
        sampler2D vTexture;//採樣器
        void main() {
            vec3 yuv;
            vec3 rgb;
            //分別取yuv各個分量的採樣紋理(r表示?)
            //
            yuv.x = texture2D(yTexture, vTextCoord).g;
            yuv.y = texture2D(uTexture, vTextCoord).g - 0.5;
            yuv.z = texture2D(vTexture, vTextCoord).g - 0.5;
            rgb = mat3(
                    1.0, 1.0, 1.0,
                    0.0, -0.39465, 2.03211,
                    1.13983, -0.5806, 0.0
            ) * yuv;
            //gl_FragColor是OpenGL內置的
            gl_FragColor = vec4(rgb, 1.0);
        }

);

2,裝載着色器:

/**
 * 加載  着色器
 * @param type     着色器類型
 * @param shaderSrc  着色源碼
 * @return
 */
GLuint LoadShader(GLenum type, const char *shaderSrc) {
    GLuint shader;
    GLint compiled;
    shader = glCreateShader(type);  //  創建 着色器 句柄
    if (shader == 0) {
        LOGE("create shader error");
        return 0;
    }

    // 裝載 着色器源碼
    glShaderSource(shader, 1, &shaderSrc, nullptr);

    // 編譯着色器
    glCompileShader(shader);

    // 檢測編譯狀態
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);

    if (!compiled) {
        GLint infoLen = 0;

        // 獲取日誌長度
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen > 1) {
            GLchar *infoLog = static_cast<GLchar *>(malloc(sizeof(GLchar) * infoLen));
            // 獲取日誌 信息
            glGetShaderInfoLog(shader, infoLen, nullptr, infoLog);
            LOGE("%s", infoLog);
            free(infoLog);
        }
        glDeleteShader(shader);
        return 0;
    }
    return shader;
}

3,創建 渲染工程

GLuint LoadProgramYUV( ){
    GLint vertexShader = LoadShader(GL_VERTEX_SHADER,vertexShader_);
    GLint fragShader = LoadShader(GL_FRAGMENT_SHADER,fragYUV420P_);
    // 創建渲染程序
    GLint program = glCreateProgram();
    if (program == 0){
        LOGE("YUV : CreateProgram failure")
        glDeleteShader(vertexShader);
        glDeleteShader(fragShader);
        return 0;
    }

    // 先渲染程序中 加入着色器
    glAttachShader(program,vertexShader);
    glAttachShader(program,fragShader);


    // 鏈接 程序
    glLinkProgram(program);
    GLint  status = 0;
    glGetProgramiv(program,GL_LINK_STATUS,&status);
    if (status == 0){
        LOGE("glLinkProgram failure")
        glDeleteProgram(program);
        return 0;
    }
    global_Context.glProgram = program;
    glUseProgram(program);
    return 1;

}

4、配置渲染邏輯

void RenderYuvConfig(uint width, uint height) {

    /* 加入三維頂點數據*/
    static float ver[] = {
            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 = static_cast<GLuint>(glGetAttribLocation(global_Context.glProgram, "aPosition"));
    glEnableVertexAttribArray(apos);
    glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 0, ver);
//
//    //加入紋理座標數據
    static float fragment[] = {
            1.0f, 0.0f,
            0.0f, 0.0f,
            1.0f, 1.0f,
            0.0f, 1.0f
    };
    GLuint aTex = static_cast<GLuint>(glGetAttribLocation(global_Context.glProgram, "aTextCoord"));
    glEnableVertexAttribArray(aTex);
    glVertexAttribPointer(aTex, 2, GL_FLOAT, GL_FALSE, 0, fragment);



    //紋理初始化
    //設置紋理層對應的對應採樣器?

    /**
     *  //獲取一致變量的存儲位置
    GLint textureUniformY = glGetUniformLocation(program, "SamplerY");
    GLint textureUniformU = glGetUniformLocation(program, "SamplerU");
    GLint textureUniformV = glGetUniformLocation(program, "SamplerV");
    //對幾個紋理採樣器變量進行設置
    glUniform1i(textureUniformY, 0);
    glUniform1i(textureUniformU, 1);
    glUniform1i(textureUniformV, 2);
     */
//     //對sampler變量,使用函數glUniform1i和glUniform1iv進行設置
    glUniform1i(glGetUniformLocation(global_Context.glProgram, "yTexture"), 0);
    glUniform1i(glGetUniformLocation(global_Context.glProgram, "uTexture"), 1);
    glUniform1i(glGetUniformLocation(global_Context.glProgram, "vTexture"), 2);






    //紋理ID
//    GLuint texts[3] = {0};
//    //創建若干個紋理對象,並且得到紋理ID
    glGenTextures(3, global_Context.mTextures);
//
//    //綁定紋理。後面的的設置和加載全部作用於當前綁定的紋理對象
//    //GL_TEXTURE0、GL_TEXTURE1、GL_TEXTURE2 的就是紋理單元,GL_TEXTURE_1D、GL_TEXTURE_2D、CUBE_MAP爲紋理目標
//    //通過 glBindTexture 函數將紋理目標和紋理綁定後,對紋理目標所進行的操作都反映到對紋理上
    glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[0]);
//    //縮小的過濾器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//    //放大的過濾器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//    //設置紋理的格式和大小
//    // 加載紋理到 OpenGL,讀入 buffer 定義的位圖數據,並把它複製到當前綁定的紋理對象
//    // 當前綁定的紋理對象就會被附加上紋理圖像。
//    //width,height表示每幾個像素公用一個yuv元素?比如width / 2表示橫向每兩個像素使用一個元素?
    glTexImage2D(GL_TEXTURE_2D,
                 0,//細節基本 默認0
                 GL_LUMINANCE,//gpu內部格式 亮度,灰度圖(這裏就是隻取一個亮度的顏色通道的意思)
                 width,//加載的紋理寬度。最好爲2的次冪(這裏對y分量數據當做指定尺寸算,但顯示尺寸會拉伸到全屏?)
                 height,//加載的紋理高度。最好爲2的次冪
                 0,//紋理邊框
                 GL_LUMINANCE,//數據的像素格式 亮度,灰度圖
                 GL_UNSIGNED_BYTE,//像素點存儲的數據類型
                 NULL //紋理的數據(先不傳)
    );
//
//    //綁定紋理
    glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[1]);
//    //縮小的過濾器
    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,//細節基本 默認0
                 GL_LUMINANCE,//gpu內部格式 亮度,灰度圖(這裏就是隻取一個顏色通道的意思)
                 width / 2,//u數據數量爲屏幕的4分之1
                 height / 2,
                 0,//邊框
                 GL_LUMINANCE,//數據的像素格式 亮度,灰度圖
                 GL_UNSIGNED_BYTE,//像素點存儲的數據類型
                 NULL //紋理的數據(先不傳)
    );
//
//    //綁定紋理
    glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[2]);
//    //縮小的過濾器
    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,//細節基本 默認0
                 GL_LUMINANCE,//gpu內部格式 亮度,灰度圖(這裏就是隻取一個顏色通道的意思)
                 width / 2,
                 height / 2,//v數據數量爲屏幕的4分之1
                 0,//邊框
                 GL_LUMINANCE,//數據的像素格式 亮度,灰度圖
                 GL_UNSIGNED_BYTE,//像素點存儲的數據類型
                 NULL //紋理的數據(先不傳)
    );


}

5,、渲染

void RenderYUV(GLuint width, GLuint height, AVFrame * frame) {

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[0]);
    glTexSubImage2D(GL_TEXTURE_2D, 0,
                    0, 0,
                    width, height,
                    GL_LUMINANCE, GL_UNSIGNED_BYTE,
                    frame->data[0]);


    // 數據 U
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[1]);
    glTexSubImage2D(GL_TEXTURE_2D, 0,
                    0, 0,
                    width / 2, height / 2,
                    GL_LUMINANCE, GL_UNSIGNED_BYTE,
                    frame->data[1]);


    // 數據 V
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[2]);
    glTexSubImage2D(GL_TEXTURE_2D, 0,
                    0, 0,
                    width / 2, height / 2,
                    GL_LUMINANCE, GL_UNSIGNED_BYTE,
                    frame->data[2]);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    eglSwapBuffers(global_Context.eglDisplay, global_Context.eglSurface);

}

上面的代碼實現了YUV420P 格式的視頻,渲染NV21 的 格式視頻,的着色器 和 配置是有所不同的這裏也貼出來:

渲染 NV21 着色器:

const char *vertexNV21Shader_ = GET_STR(
        attribute
        vec4 aPosition;//輸入的頂點座標,會在程序指定將數據輸入到該字段
        attribute
        vec2 aTextCoord;//輸入的紋理座標,會在程序指定將數據輸入到該字段
        varying
        vec2 vTextCoord;//輸出的紋理座標
        void main() {
            //這裏其實是將上下翻轉過來(因爲安卓圖片會自動上下翻轉,所以轉回來)
            vTextCoord = vec2(aTextCoord.x, 1.0 - aTextCoord.y);
//                vTextCoord = aTextCoord;
            //直接把傳入的座標值作爲傳入渲染管線。gl_Position是OpenGL內置的
            gl_Position = aPosition;
        }
);

const char *fragYUV420SP_ = GET_STR(
        precision
        mediump float;
        varying
        vec2 vTextCoord;
        //輸入的yuv三個紋理
        uniform
        sampler2D yTexture;//採樣器
        uniform
        sampler2D uvTexture;//採樣器
        const mat3 converMat = mat3(
                1.0, 1.0, 1.0,
                0.0, -0.39456, 2.03211,
                1.13983, -0.58060, 0.0
        );

        void main() {
            vec3 yuv;
            vec3 rgb;
            //分別取yuv各個分量的採樣紋理(r表示?)
            //
            yuv.x = texture2D(yTexture, vTextCoord).r;
            yuv.y = texture2D(uvTexture, vTextCoord).r - 0.5;
            yuv.z = texture2D(uvTexture, vTextCoord).a - 0.5;
            //gl_FragColor是OpenGL內置的

            gl_FragColor = vec4(converMat * yuv, 1.0);
        }

);

渲染NV21 配置:

void RenderYuvNV21Config(uint width, uint height) {

    static float ver[] = {
            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 = static_cast<GLuint>(glGetAttribLocation(global_Context.glProgram, "aPosition"));
    glEnableVertexAttribArray(apos);
    glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 0, ver);
//
//    //加入紋理座標數據
    static float fragment[] = {
            1.0f, 0.0f,
            0.0f, 0.0f,
            1.0f, 1.0f,
            0.0f, 1.0f
    };
    GLuint aTex = static_cast<GLuint>(glGetAttribLocation(global_Context.glProgram, "aTextCoord"));
    glEnableVertexAttribArray(aTex);
    glVertexAttribPointer(aTex, 2, GL_FLOAT, GL_FALSE, 0, fragment);



    //紋理初始化
    //設置紋理層對應的對應採樣器?

    /**
     *  //獲取一致變量的存儲位置
    GLint textureUniformY = glGetUniformLocation(program, "SamplerY");
    GLint textureUniformU = glGetUniformLocation(program, "SamplerU");
    GLint textureUniformV = glGetUniformLocation(program, "SamplerV");
    //對幾個紋理採樣器變量進行設置
    glUniform1i(textureUniformY, 0);
    glUniform1i(textureUniformU, 1);
    glUniform1i(textureUniformV, 2);
     */
//     //對sampler變量,使用函數glUniform1i和glUniform1iv進行設置
    glUniform1i(glGetUniformLocation(global_Context.glProgram, "yTexture"), 0);
    glUniform1i(glGetUniformLocation(global_Context.glProgram, "uvTexture"), 1);






    //紋理ID
//    GLuint texts[3] = {0};
//    //創建若干個紋理對象,並且得到紋理ID
    glGenTextures(3, global_Context.mTextures);
//
//    //綁定紋理。後面的的設置和加載全部作用於當前綁定的紋理對象
//    //GL_TEXTURE0、GL_TEXTURE1、GL_TEXTURE2 的就是紋理單元,GL_TEXTURE_1D、GL_TEXTURE_2D、CUBE_MAP爲紋理目標
//    //通過 glBindTexture 函數將紋理目標和紋理綁定後,對紋理目標所進行的操作都反映到對紋理上
    glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[0]);
//    //縮小的過濾器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//    //放大的過濾器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//    //設置紋理的格式和大小
//    // 加載紋理到 OpenGL,讀入 buffer 定義的位圖數據,並把它複製到當前綁定的紋理對象
//    // 當前綁定的紋理對象就會被附加上紋理圖像。
//    //width,height表示每幾個像素公用一個yuv元素?比如width / 2表示橫向每兩個像素使用一個元素?
    glTexImage2D(GL_TEXTURE_2D,
                 0,//細節基本 默認0
                 GL_LUMINANCE,//gpu內部格式 亮度,灰度圖(這裏就是隻取一個亮度的顏色通道的意思)
                 width,//加載的紋理寬度。最好爲2的次冪(這裏對y分量數據當做指定尺寸算,但顯示尺寸會拉伸到全屏?)
                 height,//加載的紋理高度。最好爲2的次冪
                 0,//紋理邊框
                 GL_LUMINANCE,//數據的像素格式 亮度,灰度圖
                 GL_UNSIGNED_BYTE,//像素點存儲的數據類型
                 NULL //紋理的數據(先不傳)
    );
//
//    //綁定紋理
    glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[1]);
//    //縮小的過濾器
    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,//細節基本 默認0
                 GL_LUMINANCE_ALPHA,//gpu內部格式 亮度,灰度圖(這裏就是隻取一個顏色通道的意思)
                 width / 2,//u數據數量爲屏幕的4分之1
                 height / 2,
                 0,//邊框
                 GL_LUMINANCE_ALPHA,//數據的像素格式 亮度,灰度圖
                 GL_UNSIGNED_BYTE,//像素點存儲的數據類型
                 NULL //紋理的數據(先不傳)
    );



}

這裏 其實只需要兩個紋理,我這偷懶,沒有去掉

渲染 NV21:

void RenderYUVNV21(GLuint width, GLuint height, AVFrame *frame) {

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[0]);
    glTexSubImage2D(GL_TEXTURE_2D, 0,
                    0, 0,
                    width, height,
                    GL_LUMINANCE,
                    GL_UNSIGNED_BYTE,
                    frame->data[0]);


    // 數據 U
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[1]);
    glTexSubImage2D(GL_TEXTURE_2D, 0,
                    0, 0,
                    width / 2, height / 2,
                    GL_LUMINANCE_ALPHA,
                    GL_UNSIGNED_BYTE,
                    frame->data[1]);




    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    eglSwapBuffers(global_Context.eglDisplay, global_Context.eglSurface);

}

綁定山下文環境:

int makeContext(uint width,uint height,int type) {
    EGLBoolean success = eglMakeCurrent(global_Context.eglDisplay, global_Context.eglSurface,
                                        global_Context.eglPsurface, global_Context.eglContext);
    if (!success) {
        eglDestroySurface(global_Context.eglDisplay, global_Context.eglSurface);
        eglDestroyContext(global_Context.eglDisplay, global_Context.eglContext);
        eglTerminate(global_Context.eglDisplay);
        LOGE("YUV : MakeCurrent failure")
        return -1;
    }
    if (type == 12) {  //yuvj420
        LoadProgramYUV();
        RenderYuvConfig(width,height);
    } else if (type == 23) {
        LoadProgramYUVnv21();  // nv21
        RenderYuvNV21Config(width,height);
    }
    return 0;
}

調用這個方法需要注意:必須和渲染的線程在同一線程裏面,否則會沒有顯示,黑屏,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章