Android使用OpenGL渲染ffmpeg解碼的YUV視頻數(shù)據(jù)

在《安卓使用SurfaceView繪制ffmpeg解碼的視頻數(shù)據(jù)》雖然我們成功地實(shí)現(xiàn)了視頻的渲染,
但是在YUV轉(zhuǎn)換成RGB的時(shí)候末早,我們調(diào)用了ffmpeg內(nèi)部的轉(zhuǎn)換函數(shù),這里面包含大量的計(jì)算轉(zhuǎn)換,
所以是很耗費(fèi)CPU性能的逊彭。

今天我們來學(xué)習(xí)一下如何將YUV轉(zhuǎn)換RGB的功能轉(zhuǎn)換到GPU中去執(zhí)行,減少CPU的計(jì)算工作量构订,達(dá)到性能優(yōu)化的目的侮叮。

解決方案是使用OpenGL渲染,將YUV轉(zhuǎn)換RGB的功能交由著色器去處理悼瘾。

Android中引入OpenGL

下面以O(shè)penGL 2.0為例囊榜。

1、 引入庫文件

CMakeLists.txt引入GLESv2EGL庫(在ndk中內(nèi)置)亥宿。
直接target_link_libraries加入即可:

target_link_libraries(
                      ........
                       #引入opengl的相關(guān)庫
                       GLESv2
                       EGL
                          )

2卸勺、 引入相關(guān)的頭文件

需要引入的頭文件

#include <EGL/egl.h>
#include <GLES2/gl2.h>

至此,我們的安卓OpenGL環(huán)境就算引入成功了烫扼。

OpenGL渲染環(huán)境搭建

1曙求、 EGLDisplay
OpenGL(移動(dòng)端稱作的是EGL)要知道把目標(biāo)內(nèi)容繪制在哪里。這就是EGLDisplay所需的功能映企。
EGLDisplay是一個(gè)封裝系統(tǒng)物理屏幕的數(shù)據(jù)類型(可以理解為繪制目標(biāo)的一個(gè)抽象)悟狱,通常會(huì)調(diào)用eglGetDisplay方法返回EGLDisplay來作為OpenGL ES渲染的目標(biāo)。然后通過eglInitialize初始化顯示設(shè)備堰氓。

代碼如下:

//1 EGL display創(chuàng)建和初始化
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (display == EGL_NO_DISPLAY) {
        LOGE("eglGetDisplay failed!");
        return;
    }
    if (EGL_TRUE != eglInitialize(display, 0, 0)) {
        LOGE("eglInitialize failed!");
        return;
    }

2挤渐、 EGLConfig
EGL有了Display之后,它就可以將OpenGL ES的輸出和設(shè)備的屏幕橋接起來豆赏,但是需要指定一些配置項(xiàng)挣菲,這時(shí)候EGLConfig就閃亮登場啦富稻。

//輸出配置
    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_NONE
    };
    if (EGL_TRUE != eglChooseConfig(display, configSpec, &config, 1, &configNum)) {
        LOGE("eglChooseConfig failed!");
        return;
    }

3、 創(chuàng)建EGLSurface
有了顯示設(shè)備白胀,那么如何將設(shè)備的屏幕與EGL鏈接起來呢椭赋?EGLSurface粉墨登場。

//獲取原始窗口
//surface是外部的SurfaceView傳遞進(jìn)來的
ANativeWindow *nwin = ANativeWindow_fromSurface(env, surface);
//創(chuàng)建surface
EGLSurface winsurface = eglCreateWindowSurface(display, config, nwin, 0);
    if (winsurface == EGL_NO_SURFACE) {
        LOGE("eglCreateWindowSurface failed!");
        return;
    }

4或杠、 EGLContext
OpenGL所創(chuàng)建的資源, 其實(shí)對程序員可見的僅僅是ID而已, 要操作其中內(nèi)容就需要依賴于EGLContext上下文哪怔。

 //context 創(chuàng)建關(guān)聯(lián)的上下文
    const EGLint ctxAttr[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
    };
    EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr);
    if (context == EGL_NO_CONTEXT) {
        LOGE("eglCreateContext failed!");
        return;
    }
    if (EGL_TRUE != eglMakeCurrent(display, winsurface, winsurface, context)) {
        LOGE("eglMakeCurrent failed!");
        return;
    }

4、 著色器
所有的渲染都需要位置信息與色彩信息這兩個(gè)信息才能成功渲染處理向抢。頂點(diǎn)著色器和片元著色器就完成了這兩個(gè)功能认境。

頂點(diǎn)著色器:

 attribute  vec4 aPosition; //頂點(diǎn)坐標(biāo),在外部獲取傳遞進(jìn)來

attribute vec2 aTexCoord; //材質(zhì)(紋理)頂點(diǎn)坐標(biāo)

varying vec2 vTexCoord;   //輸出的材質(zhì)(紋理)坐標(biāo)挟鸠,給片元著色器使用

void main() {
      //紋理坐標(biāo)轉(zhuǎn)換叉信,以左上角為原點(diǎn)的紋理坐標(biāo)轉(zhuǎn)換成以左下角為原點(diǎn)的紋理坐標(biāo),
      // 比如以左上角為原點(diǎn)的(0艘希,0)對應(yīng)以左下角為原點(diǎn)的紋理坐標(biāo)的(0硼身,1)
      vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
      gl_Position = aPosition;
    }

片元著色器:

precision mediump float;    //精度

        varying vec2 vTexCoord;     //頂點(diǎn)著色器傳遞的坐標(biāo),相同名字opengl會(huì)自動(dòng)關(guān)聯(lián)

        uniform sampler2D yTexture; //輸入的材質(zhì)(不透明灰度覆享,單像素)

        uniform sampler2D uTexture;

        uniform sampler2D vTexture;
        void main() {
            vec3 yuv;
            vec3 rgb;
            yuv.r = texture2D(yTexture, vTexCoord).r; // y分量
            // 因?yàn)閁V的默認(rèn)值是127佳遂,所以我們這里要減去0.5(OpenGLES的Shader中會(huì)把內(nèi)存中0~255的整數(shù)數(shù)值換算為0.0~1.0的浮點(diǎn)數(shù)值)
            yuv.g = texture2D(uTexture, vTexCoord).r - 0.5; // u分量
            yuv.b = texture2D(vTexture, vTexCoord).r - 0.5; // v分量
            // yuv轉(zhuǎn)換成rgb,兩種方法撒顿,一種是RGB按照特定換算公式單獨(dú)轉(zhuǎn)換
            // 另外一種是使用矩陣轉(zhuǎn)換
            rgb = mat3(1.0, 1.0, 1.0,
                       0.0, -0.39465, 2.03211,
                       1.13983, -0.58060, 0.0) * yuv;
            //輸出像素顏色
            gl_FragColor = vec4(rgb, 1.0);
        }

著色器編寫好之后如何使用呢丑罪?我們看一張圖:


著色器編譯關(guān)聯(lián)

根據(jù)圖片流程,我們很方便就創(chuàng)建編譯并且鏈接好著色器凤壁。

GLint InitShader(const char *code, GLint type) {
    //創(chuàng)建shader
    GLint sh = glCreateShader(type);
    if (sh == 0) {
        LOGE("glCreateShader %d failed!", type);
        return 0;
    }
    //加載shader
    glShaderSource(sh,
                   1,    //shader數(shù)量
                   &code, //shader代碼
                   0);   //代碼長度
    //編譯shader
    glCompileShader(sh);

    //獲取編譯情況
    GLint status;
    glGetShaderiv(sh, GL_COMPILE_STATUS, &status);
    if (status == 0) {
        LOGE("glCompileShader failed!");
        return 0;
    }
    LOGE("glCompileShader success!");
    return sh;
}

........此處省略若干代碼

    //頂點(diǎn)和片元shader初始化
    //頂點(diǎn)shader初始化
    GLint vsh = InitShader(vertexShader, GL_VERTEX_SHADER);
    //片元yuv420 shader初始化
    GLint fsh = InitShader(fragYUV420P, GL_FRAGMENT_SHADER);


    /////////////////////////////////////////////////////////////
    //創(chuàng)建渲染程序
    GLint program = glCreateProgram();
    if (program == 0) {
        LOGE("glCreateProgram failed!");
        return;
    }
    //渲染程序中加入著色器代碼
    glAttachShader(program, vsh);
    glAttachShader(program, fsh);

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


    //加入三維頂點(diǎn)數(shù)據(jù) 兩個(gè)三角形組成正方形
    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);
    //傳遞頂點(diǎn)
    glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 12, vers);

    //加入材質(zhì)坐標(biāo)數(shù)據(jù)
    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);

    //材質(zhì)紋理初始化
    //設(shè)置紋理層
    glUniform1i(glGetUniformLocation(program, "yTexture"), 0); //對于紋理第1層
    glUniform1i(glGetUniformLocation(program, "uTexture"), 1); //對于紋理第2層
    glUniform1i(glGetUniformLocation(program, "vTexture"), 2); //對于紋理第3層

    //創(chuàng)建opengl紋理
    GLuint texts[3] = {0};
    //創(chuàng)建三個(gè)紋理
    glGenTextures(3, texts);

    //設(shè)置紋理屬性
    glBindTexture(GL_TEXTURE_2D, texts[0]);
    //縮小的過濾器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //設(shè)置紋理的格式和大小
    glTexImage2D(GL_TEXTURE_2D,
                 0,           //細(xì)節(jié)基本 0默認(rèn)
                 GL_LUMINANCE,//gpu內(nèi)部格式 亮度吩屹,灰度圖
                 width, height, //拉升到全屏
                 0,             //邊框
                 GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度,灰度圖 要與上面一致
                 GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
                 NULL                    //紋理的數(shù)據(jù)
    );

    //設(shè)置紋理屬性
    glBindTexture(GL_TEXTURE_2D, texts[1]);
    //縮小的過濾器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //設(shè)置紋理的格式和大小
    glTexImage2D(GL_TEXTURE_2D,
                 0,           //細(xì)節(jié)基本 0默認(rèn)
                 GL_LUMINANCE,//gpu內(nèi)部格式 亮度客扎,灰度圖
                 width / 2, height / 2, //拉升到全屏
                 0,             //邊框
                 GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度祟峦,灰度圖 要與上面一致
                 GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
                 NULL                    //紋理的數(shù)據(jù)
    );

    //設(shè)置紋理屬性
    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);
    //設(shè)置紋理的格式和大小
    glTexImage2D(GL_TEXTURE_2D,
                 0,           //細(xì)節(jié)基本 0默認(rèn)
                 GL_LUMINANCE,//gpu內(nèi)部格式 亮度罚斗,灰度圖
                 width / 2, height / 2, //拉升到全屏
                 0,             //邊框
                 GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度徙鱼,灰度圖 要與上面一致
                 GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
                 NULL                    //紋理的數(shù)據(jù)
    );


    //////////////////////////////////////////////////////
    ////紋理的修改和顯示
    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];

5、 傳遞數(shù)據(jù)
到了這一步针姿,我們的渲染環(huán)境算是搭建完畢了袱吆,下面就是通過CPU傳遞數(shù)據(jù)到GPU進(jìn)行渲染。簡單地說就是獲取在著色器中定義的變量并且對其賦值距淫,然后調(diào)用繪制的API即可绞绒。
主要代碼:

// 解碼得到Y(jié)UV數(shù)據(jù)

                    // 數(shù)據(jù)Y
                    buf[0] = frame->data[0];

                    memcpy(buf[0],frame->data[0],width*height);
                    // 數(shù)據(jù)U
                    memcpy(buf[1],frame->data[1],width*height/4);

                    // 數(shù)據(jù)V
                    memcpy(buf[2],frame->data[2],width*height/4);

                    //激活第1層紋理,綁定到創(chuàng)建的opengl紋理
                    glActiveTexture(GL_TEXTURE0);
                    glBindTexture(GL_TEXTURE_2D,texts[0]);
                    //替換紋理內(nèi)容
                    glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[0]);


                    //激活第2層紋理,綁定到創(chuàng)建的opengl紋理
                    glActiveTexture(GL_TEXTURE0+1);
                    glBindTexture(GL_TEXTURE_2D,texts[1]);
                    //替換紋理內(nèi)容
                    glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width/2,height/2,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[1]);


                    //激活第2層紋理,綁定到創(chuàng)建的opengl紋理
                    glActiveTexture(GL_TEXTURE0+2);
                    glBindTexture(GL_TEXTURE_2D,texts[2]);
                    //替換紋理內(nèi)容
                    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(display,winsurface);

至此,我們的渲染過程就完成了榕暇,與前面的ffmpeg解碼為YUV數(shù)據(jù)聯(lián)系起來蓬衡,實(shí)現(xiàn)一邊解碼一邊渲染數(shù)據(jù)喻杈。通過對比筆者很明顯覺察到使用OpenGL渲染的視頻畫面流暢很多。而且對比兩種渲染方式的CPU使用率也發(fā)現(xiàn)OpenGL的渲染方式確實(shí)使得CPU的使用率大大降低狰晚。

最后貼一下結(jié)合ffmpeg從解碼到渲染全過程的完整代碼:


#include <jni.h>
#include <string>


#include "FlyLog.h"

#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>

#include <android/native_window_jni.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>

// 因?yàn)閒fmpeg是純C代碼筒饰,要在cpp中使用則需要使用 extern "C"
extern "C" {
#include "libavutil/avutil.h"

#include <libavformat/avformat.h>

#include <libswscale/swscale.h>

}



//頂點(diǎn)著色器glsl的宏
// 第二個(gè)#號(hào)的意思是自動(dòng)鏈接字符串,而不用增加引號(hào)壁晒,參考ijkplayer的寫法

#define GET_STR(x) #x

static const char *vertexShader = GET_STR(

        attribute  vec4 aPosition; //頂點(diǎn)坐標(biāo)瓷们,在外部獲取傳遞進(jìn)來

        attribute vec2 aTexCoord; //材質(zhì)(紋理)頂點(diǎn)坐標(biāo)

        varying vec2 vTexCoord;   //輸出的材質(zhì)(紋理)坐標(biāo),給片元著色器使用
        void main() {
            //紋理坐標(biāo)轉(zhuǎn)換秒咐,以左上角為原點(diǎn)的紋理坐標(biāo)轉(zhuǎn)換成以左下角為原點(diǎn)的紋理坐標(biāo)谬晕,
            // 比如以左上角為原點(diǎn)的(0,0)對應(yīng)以左下角為原點(diǎn)的紋理坐標(biāo)的(0携取,1)
            vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
            gl_Position = aPosition;
        }
);

//片元著色器,軟解碼和部分x86硬解碼解碼得出來的格式是YUV420p

static const char *fragYUV420P = GET_STR(

        precision mediump float;    //精度

        varying vec2 vTexCoord;     //頂點(diǎn)著色器傳遞的坐標(biāo)攒钳,相同名字opengl會(huì)自動(dòng)關(guān)聯(lián)

        uniform sampler2D yTexture; //輸入的材質(zhì)(不透明灰度,單像素)

        uniform sampler2D uTexture;

        uniform sampler2D vTexture;
        void main() {
            vec3 yuv;
            vec3 rgb;
            yuv.r = texture2D(yTexture, vTexCoord).r; // y分量
            // 因?yàn)閁V的默認(rèn)值是127雷滋,所以我們這里要減去0.5(OpenGLES的Shader中會(huì)把內(nèi)存中0~255的整數(shù)數(shù)值換算為0.0~1.0的浮點(diǎn)數(shù)值)
            yuv.g = texture2D(uTexture, vTexCoord).r - 0.5; // u分量
            yuv.b = texture2D(vTexture, vTexCoord).r - 0.5; // v分量
            // yuv轉(zhuǎn)換成rgb夕玩,兩種方法,一種是RGB按照特定換算公式單獨(dú)轉(zhuǎn)換
            // 另外一種是使用矩陣轉(zhuǎn)換
            rgb = mat3(1.0, 1.0, 1.0,
                       0.0, -0.39465, 2.03211,
                       1.13983, -0.58060, 0.0) * yuv;
            //輸出像素顏色
            gl_FragColor = vec4(rgb, 1.0);
        }
);

GLint InitShader(const char *code, GLint type) {
    //創(chuàng)建shader
    GLint sh = glCreateShader(type);
    if (sh == 0) {
        LOGE("glCreateShader %d failed!", type);
        return 0;
    }
    //加載shader
    glShaderSource(sh,
                   1,    //shader數(shù)量
                   &code, //shader代碼
                   0);   //代碼長度
    //編譯shader
    glCompileShader(sh);

    //獲取編譯情況
    GLint status;
    glGetShaderiv(sh, GL_COMPILE_STATUS, &status);
    if (status == 0) {
        LOGE("glCompileShader failed!");
        return 0;
    }
    LOGE("glCompileShader success!");
    return sh;
}


/**
 * 將數(shù)據(jù)轉(zhuǎn)換成double類型的一個(gè)方法
 * @param r
 * @return
 */
static double r2d(AVRational r) {
    return r.num == 0 || r.den == 0 ? 0 : (double) r.num / (double) r.den;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_flyer_ffmpeg_FlyPlayer_playVideoByOpenGL(JNIEnv *env, jobject thiz, jstring video_path,
                                                  jobject surface) {


    const char *path = env->GetStringUTFChars(video_path, 0);

    AVFormatContext *fmt_ctx;
    // 初始化格式化上下文
    fmt_ctx = avformat_alloc_context();

    // 使用ffmpeg打開文件
    int re = avformat_open_input(&fmt_ctx, path, nullptr, nullptr);
    if (re != 0) {
        LOGE("打開文件失斁颉:%s", av_err2str(re));
        return;
    }

    //探測流索引
    re = avformat_find_stream_info(fmt_ctx, nullptr);

    if (re < 0) {
        LOGE("索引探測失斄敲稀:%s", av_err2str(re));
        return;
    }

    //尋找視頻流索引
    int v_idx = av_find_best_stream(
            fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);

    if (v_idx == -1) {
        LOGE("獲取視頻流索引失敗");
        return;
    }
    //解碼器參數(shù)
    AVCodecParameters *c_par;
    //解碼器上下文
    AVCodecContext *cc_ctx;
    //聲明一個(gè)解碼器
    const AVCodec *codec;

    c_par = fmt_ctx->streams[v_idx]->codecpar;

    //通過id查找解碼器
    codec = avcodec_find_decoder(c_par->codec_id);

    if (!codec) {

        LOGE("查找解碼器失敗");
        return;
    }

    //用參數(shù)c_par實(shí)例化編解碼器上下文,尸昧,并打開編解碼器
    cc_ctx = avcodec_alloc_context3(codec);

    // 關(guān)聯(lián)解碼器上下文
    re = avcodec_parameters_to_context(cc_ctx, c_par);

    if (re < 0) {
        LOGE("解碼器上下文關(guān)聯(lián)失敗:%s", av_err2str(re));
        return;
    }

    //打開解碼器
    re = avcodec_open2(cc_ctx, codec, nullptr);

    if (re != 0) {
        LOGE("打開解碼器失敗:%s", av_err2str(re));
        return;
    }

    // 獲取視頻的寬高,也可以通過解碼器獲取
    AVStream *as = fmt_ctx->streams[v_idx];
    int width = as->codecpar->width;
    int height = as->codecpar->height;

    LOGE("width:%d", width);
    LOGE("height:%d", height);

    //數(shù)據(jù)包
    AVPacket *pkt;
    //數(shù)據(jù)幀
    AVFrame *frame;

    //初始化
    pkt = av_packet_alloc();
    frame = av_frame_alloc();

    //1 獲取原始窗口
    ANativeWindow *nwin = ANativeWindow_fromSurface(env, surface);

    ////////////////////
    ///EGL
    //1 EGL display創(chuàng)建和初始化
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (display == EGL_NO_DISPLAY) {
        LOGE("eglGetDisplay failed!");
        return;
    }
    if (EGL_TRUE != eglInitialize(display, 0, 0)) {
        LOGE("eglInitialize failed!");
        return;
    }
    //2 surface
    //2-1 surface窗口配置
    //輸出配置
    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_NONE
    };
    if (EGL_TRUE != eglChooseConfig(display, configSpec, &config, 1, &configNum)) {
        LOGE("eglChooseConfig failed!");
        return;
    }
    //創(chuàng)建surface
    EGLSurface winsurface = eglCreateWindowSurface(display, config, nwin, 0);
    if (winsurface == EGL_NO_SURFACE) {
        LOGE("eglCreateWindowSurface failed!");
        return;
    }

    //3 context 創(chuàng)建關(guān)聯(lián)的上下文
    const EGLint ctxAttr[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
    };
    EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr);
    if (context == EGL_NO_CONTEXT) {
        LOGE("eglCreateContext failed!");
        return;
    }
    if (EGL_TRUE != eglMakeCurrent(display, winsurface, winsurface, context)) {
        LOGE("eglMakeCurrent failed!");
        return;
    }

    LOGE("EGL Init Success!");

    //頂點(diǎn)和片元shader初始化
    //頂點(diǎn)shader初始化
    GLint vsh = InitShader(vertexShader, GL_VERTEX_SHADER);
    //片元yuv420 shader初始化
    GLint fsh = InitShader(fragYUV420P, GL_FRAGMENT_SHADER);


    /////////////////////////////////////////////////////////////
    //創(chuàng)建渲染程序
    GLint program = glCreateProgram();
    if (program == 0) {
        LOGE("glCreateProgram failed!");
        return;
    }
    //渲染程序中加入著色器代碼
    glAttachShader(program, vsh);
    glAttachShader(program, fsh);

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


    //加入三維頂點(diǎn)數(shù)據(jù) 兩個(gè)三角形組成正方形
    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);
    //傳遞頂點(diǎn)
    glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 12, vers);

    //加入材質(zhì)坐標(biāo)數(shù)據(jù)
    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);

    //材質(zhì)紋理初始化
    //設(shè)置紋理層
    glUniform1i(glGetUniformLocation(program, "yTexture"), 0); //對于紋理第1層
    glUniform1i(glGetUniformLocation(program, "uTexture"), 1); //對于紋理第2層
    glUniform1i(glGetUniformLocation(program, "vTexture"), 2); //對于紋理第3層

    //創(chuàng)建opengl紋理
    GLuint texts[3] = {0};
    //創(chuàng)建三個(gè)紋理
    glGenTextures(3, texts);

    //設(shè)置紋理屬性
    glBindTexture(GL_TEXTURE_2D, texts[0]);
    //縮小的過濾器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //設(shè)置紋理的格式和大小
    glTexImage2D(GL_TEXTURE_2D,
                 0,           //細(xì)節(jié)基本 0默認(rèn)
                 GL_LUMINANCE,//gpu內(nèi)部格式 亮度揩页,灰度圖
                 width, height, //拉升到全屏
                 0,             //邊框
                 GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度,灰度圖 要與上面一致
                 GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
                 NULL                    //紋理的數(shù)據(jù)
    );

    //設(shè)置紋理屬性
    glBindTexture(GL_TEXTURE_2D, texts[1]);
    //縮小的過濾器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //設(shè)置紋理的格式和大小
    glTexImage2D(GL_TEXTURE_2D,
                 0,           //細(xì)節(jié)基本 0默認(rèn)
                 GL_LUMINANCE,//gpu內(nèi)部格式 亮度烹俗,灰度圖
                 width / 2, height / 2, //拉升到全屏
                 0,             //邊框
                 GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度爆侣,灰度圖 要與上面一致
                 GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
                 NULL                    //紋理的數(shù)據(jù)
    );

    //設(shè)置紋理屬性
    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);
    //設(shè)置紋理的格式和大小
    glTexImage2D(GL_TEXTURE_2D,
                 0,           //細(xì)節(jié)基本 0默認(rèn)
                 GL_LUMINANCE,//gpu內(nèi)部格式 亮度,灰度圖
                 width / 2, height / 2, //拉升到全屏
                 0,             //邊框
                 GL_LUMINANCE,//數(shù)據(jù)的像素格式 亮度幢妄,灰度圖 要與上面一致
                 GL_UNSIGNED_BYTE, //像素的數(shù)據(jù)類型
                 NULL                    //紋理的數(shù)據(jù)
    );


    //////////////////////////////////////////////////////
    ////紋理的修改和顯示
    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];


    while (av_read_frame(fmt_ctx, pkt) >= 0) {//持續(xù)讀幀
        // 只解碼視頻流
        if (pkt->stream_index == v_idx) {

            //發(fā)送數(shù)據(jù)包到解碼器
            avcodec_send_packet(cc_ctx, pkt);

            //清理
            av_packet_unref(pkt);

            //這里為什么要使用一個(gè)for循環(huán)呢兔仰?
            // 因?yàn)閍vcodec_send_packet和avcodec_receive_frame并不是一對一的關(guān)系的
            //一個(gè)avcodec_send_packet可能會(huì)出發(fā)多個(gè)avcodec_receive_frame
            for (;;) {
                // 接受解碼的數(shù)據(jù)
                re = avcodec_receive_frame(cc_ctx, frame);
                if (re != 0) {
                    break;
                } else {

                    // 解碼得到Y(jié)UV數(shù)據(jù)

                    // 數(shù)據(jù)Y
                    buf[0] = frame->data[0];

                    memcpy(buf[0],frame->data[0],width*height);
                    // 數(shù)據(jù)U
                    memcpy(buf[1],frame->data[1],width*height/4);

                    // 數(shù)據(jù)V
                    memcpy(buf[2],frame->data[2],width*height/4);

                    //激活第1層紋理,綁定到創(chuàng)建的opengl紋理
                    glActiveTexture(GL_TEXTURE0);
                    glBindTexture(GL_TEXTURE_2D,texts[0]);
                    //替換紋理內(nèi)容
                    glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[0]);


                    //激活第2層紋理,綁定到創(chuàng)建的opengl紋理
                    glActiveTexture(GL_TEXTURE0+1);
                    glBindTexture(GL_TEXTURE_2D,texts[1]);
                    //替換紋理內(nèi)容
                    glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width/2,height/2,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[1]);


                    //激活第2層紋理,綁定到創(chuàng)建的opengl紋理
                    glActiveTexture(GL_TEXTURE0+2);
                    glBindTexture(GL_TEXTURE_2D,texts[2]);
                    //替換紋理內(nèi)容
                    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(display,winsurface);

                }
            }

        }
    }
    //關(guān)閉環(huán)境
    avcodec_free_context(&cc_ctx);
    // 釋放資源
    av_frame_free(&frame);
    av_packet_free(&pkt);

    avformat_free_context(fmt_ctx);

    LOGE("播放完畢");

    env->ReleaseStringUTFChars(video_path, path);

}

遇到的問題

筆者測試了兩個(gè)不同的視頻發(fā)現(xiàn)一個(gè)能播放,另外一個(gè)花屏蕉鸳,看不出圖像乎赴。

查找了一些資料至今仍找不到問題所在,兩個(gè)視頻使用SurfaceView渲染都是可以的潮尝,說明可能不是解碼的問題榕吼,估計(jì)是渲染程序的問題。而且兩個(gè)視頻解碼出來的YUV數(shù)據(jù)格式不一樣勉失,一個(gè)yuvj420p羹蚣,這個(gè)可以正常使用OpenGL渲染,一個(gè)是yuv420p乱凿,這個(gè)就不能渲染顽素,花屏咽弦。

懇親大神不吝賜教。

結(jié)束

最后如果你對音視頻開發(fā)感興趣可掃碼關(guān)注胁出,筆者在各個(gè)知識(shí)點(diǎn)學(xué)習(xí)完畢之后也會(huì)使用ffmepg從零開始編寫一個(gè)多媒體播放器离唬,包括本地播放及網(wǎng)絡(luò)流播放等等。歡迎關(guān)注划鸽,后續(xù)我們共同探討输莺,共同進(jìn)步。


image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裸诽,一起剝皮案震驚了整個(gè)濱河市嫂用,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丈冬,老刑警劉巖嘱函,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異埂蕊,居然都是意外死亡往弓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門蓄氧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來函似,“玉大人,你說我怎么就攤上這事喉童∑材” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵堂氯,是天一觀的道長蔑担。 經(jīng)常有香客問我,道長咽白,這世上最難降的妖魔是什么啤握? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮晶框,結(jié)果婚禮上排抬,老公的妹妹穿的比我還像新娘。我一直安慰自己三妈,他們只是感情好畜埋,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畴蒲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪对室。 梳的紋絲不亂的頭發(fā)上模燥,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天咖祭,我揣著相機(jī)與錄音,去河邊找鬼蔫骂。 笑死么翰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辽旋。 我是一名探鬼主播浩嫌,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼补胚!你這毒婦竟也來了码耐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤溶其,失蹤者是張志新(化名)和其女友劉穎骚腥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓶逃,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡束铭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厢绝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片契沫。...
    茶點(diǎn)故事閱讀 40,865評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖昔汉,靈堂內(nèi)的尸體忽然破棺而出埠褪,到底是詐尸還是另有隱情,我是刑警寧澤挤庇,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布钞速,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谆膳。R本人自食惡果不足惜茵瀑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望驾凶。 院中可真熱鬧,春花似錦掷酗、人聲如沸调违。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽技肩。三九已至,卻和暖如春浮声,著一層夾襖步出監(jiān)牢的瞬間虚婿,已是汗流浹背旋奢。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留然痊,地道東北人至朗。 一個(gè)月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像剧浸,于是被迫代替她去往敵國和親锹引。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容