Android 基於ffmpeg開發簡易播放器 - EGL和OpenGLESGLES顯示YUV視訊

貓尾巴發表於2018-06-11

EGL和OpenGLESGLES顯示YUV視訊

1.EGL

EGL是OpenGL ES與系統原始視窗的適配層:

Android 基於ffmpeg開發簡易播放器 - EGL和OpenGLESGLES顯示YUV視訊
Display:用於與原生視窗建立連線。 Surface:用於渲染的區域。 Context:建立渲染上下文。指的是OpenGL ES專案執行需要的所有資料結構。如:定點,著色器,頂點資料矩陣。

2.GLSL

頂點著色器針對每個頂點執行一次,用於確定頂點的位置。

片元著色器針對每個片元(畫素)執行一次,用於確定片元(畫素)的顏色。

3.Android向底層傳遞Surface控制程式碼

public class XPlay extends GLSurfaceView implements Runnable,SurfaceHolder.Callback
{
    public XPlay(Context context, AttributeSet attrs) {
        super( context, attrs );
    }

    @Override
    public void run() {
        Open("/sdcard/outyuv",getHolder().getSurface());

    }
    
    @Override
    public void surfaceCreated(SurfaceHolder var1)
    {
        new Thread( this ).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder var1, int var2, int var3, int var4)
    {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder var1)
    {

    }
    public native void Open(String url,Object surface);
}
複製程式碼

4.獲取原始視窗

#include <android/native_window_jni.h>
複製程式碼
extern "C"
JNIEXPORT void JNICALL
Java_aplay_testopengles_XPlay_Open(JNIEnv *env, jobject instance, jstring url_, jobject surface) {
    //1 獲取原始視窗
    ANativeWindow *nwin = ANativeWindow_fromSurface(env,surface);
}
複製程式碼

ANativeWindow_fromSurface():返回與Java Surface物件關聯的ANativeWindow,通過原生程式碼與之互動。這獲得了返回的ANativeWindow的引用; 使用結束之後要使用ANativeWindow_release(),這樣它才不會洩漏。

ANativeWindow * ANativeWindow_fromSurface(
  JNIEnv *env,
  jobject surface
)
複製程式碼

ANativeWindow:不透明型別,提供對本地視窗的訪問。

struct ANativeWindow
{
#ifdef __cplusplus
    ANativeWindow()
        : flags(0), minSwapInterval(0), maxSwapInterval(0), xdpi(0), ydpi(0)
    {
        common.magic = ANDROID_NATIVE_WINDOW_MAGIC;
        common.version = sizeof(ANativeWindow);
        memset(common.reserved, 0, sizeof(common.reserved));
    }
    /* Implement the methods that sp<ANativeWindow> expects so that it
       can be used to automatically refcount ANativeWindow's. */
    void incStrong(const void* id) const {
        common.incRef(const_cast<android_native_base_t*>(&common));
    }
    void decStrong(const void* id) const {
        common.decRef(const_cast<android_native_base_t*>(&common));
    }
#endif
    struct android_native_base_t common;
    /* flags describing some attributes of this surface or its updater */
    const uint32_t flags;
    /* min swap interval supported by this updated */
    const int   minSwapInterval;
    /* max swap interval supported by this updated */
    const int   maxSwapInterval;
    /* horizontal and vertical resolution in DPI */
    const float xdpi;
    const float ydpi;
    /* Some storage reserved for the OEM's driver. */
    intptr_t    oem[4];
    /*
     * Set the swap interval for this surface.
     *
     * Returns 0 on success or -errno on error.
     */
    int     (*setSwapInterval)(struct ANativeWindow* window,
                int interval);
    /*
     * Hook called by EGL to acquire a buffer. After this call, the buffer
     * is not locked, so its content cannot be modified. This call may block if
     * no buffers are available.
     *
     * The window holds a reference to the buffer between dequeueBuffer and
     * either queueBuffer or cancelBuffer, so clients only need their own
     * reference if they might use the buffer after queueing or canceling it.
     * Holding a reference to a buffer after queueing or canceling it is only
     * allowed if a specific buffer count has been set.
     *
     * Returns 0 on success or -errno on error.
     */
    int     (*dequeueBuffer)(struct ANativeWindow* window,
                struct ANativeWindowBuffer** buffer);
    /*
     * hook called by EGL to lock a buffer. This MUST be called before modifying
     * the content of a buffer. The buffer must have been acquired with
     * dequeueBuffer first.
     *
     * Returns 0 on success or -errno on error.
     */
    int     (*lockBuffer)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer);
    /*
     * Hook called by EGL when modifications to the render buffer are done.
     * This unlocks and post the buffer.
     *
     * The window holds a reference to the buffer between dequeueBuffer and
     * either queueBuffer or cancelBuffer, so clients only need their own
     * reference if they might use the buffer after queueing or canceling it.
     * Holding a reference to a buffer after queueing or canceling it is only
     * allowed if a specific buffer count has been set.
     *
     * Buffers MUST be queued in the same order than they were dequeued.
     *
     * Returns 0 on success or -errno on error.
     */
    int     (*queueBuffer)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer);
    /*
     * hook used to retrieve information about the native window.
     *
     * Returns 0 on success or -errno on error.
     */
    int     (*query)(const struct ANativeWindow* window,
                int what, int* value);
    /*
     * hook used to perform various operations on the surface.
     * (*perform)() is a generic mechanism to add functionality to
     * ANativeWindow while keeping backward binary compatibility.
     *
     * DO NOT CALL THIS HOOK DIRECTLY.  Instead, use the helper functions
     * defined below.
     *
     *  (*perform)() returns -ENOENT if the 'what' parameter is not supported
     *  by the surface's implementation.
     *
     * The valid operations are:
     *     NATIVE_WINDOW_SET_USAGE
     *     NATIVE_WINDOW_CONNECT               (deprecated)
     *     NATIVE_WINDOW_DISCONNECT            (deprecated)
     *     NATIVE_WINDOW_SET_CROP              (private)
     *     NATIVE_WINDOW_SET_BUFFER_COUNT
     *     NATIVE_WINDOW_SET_BUFFERS_GEOMETRY  (deprecated)
     *     NATIVE_WINDOW_SET_BUFFERS_TRANSFORM
     *     NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP
     *     NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS
     *     NATIVE_WINDOW_SET_BUFFERS_FORMAT
     *     NATIVE_WINDOW_SET_SCALING_MODE       (private)
     *     NATIVE_WINDOW_LOCK                   (private)
     *     NATIVE_WINDOW_UNLOCK_AND_POST        (private)
     *     NATIVE_WINDOW_API_CONNECT            (private)
     *     NATIVE_WINDOW_API_DISCONNECT         (private)
     *     NATIVE_WINDOW_SET_BUFFERS_USER_DIMENSIONS (private)
     *     NATIVE_WINDOW_SET_POST_TRANSFORM_CROP (private)
     *
     */
    int     (*perform)(struct ANativeWindow* window,
                int operation, ... );
    /*
     * Hook used to cancel a buffer that has been dequeued.
     * No synchronization is performed between dequeue() and cancel(), so
     * either external synchronization is needed, or these functions must be
     * called from the same thread.
     *
     * The window holds a reference to the buffer between dequeueBuffer and
     * either queueBuffer or cancelBuffer, so clients only need their own
     * reference if they might use the buffer after queueing or canceling it.
     * Holding a reference to a buffer after queueing or canceling it is only
     * allowed if a specific buffer count has been set.
     */
    int     (*cancelBuffer)(struct ANativeWindow* window,
                struct ANativeWindowBuffer* buffer);
    void* reserved_proc[2];
};
複製程式碼

5.初始化EGL

#include <EGL/egl.h>
複製程式碼
///EGL
//1 EGL display建立和初始化
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(display == EGL_NO_DISPLAY)
{
    LOGD("eglGetDisplay failed!");
    return;
}
if(EGL_TRUE != eglInitialize(display,0,0))
{
    LOGD("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))
{
    LOGD("eglChooseConfig failed!");
    return;
}
//建立surface
EGLSurface winsurface = eglCreateWindowSurface(display,config,nwin,0);
if(winsurface == EGL_NO_SURFACE)
{
    LOGD("eglCreateWindowSurface failed!");
    return;
}

//3 context 建立關聯的上下文
const EGLint ctxAttr[] = {
        EGL_CONTEXT_CLIENT_VERSION,2,EGL_NONE
};
EGLContext context = eglCreateContext(display,config,EGL_NO_CONTEXT,ctxAttr);
if(context == EGL_NO_CONTEXT)
{
    LOGD("eglCreateContext failed!");
    return;
}
if(EGL_TRUE != eglMakeCurrent(display,winsurface,winsurface,context))
{
    LOGD("eglMakeCurrent failed!");
    return;
}

LOGD("EGL Init Success!");
複製程式碼

EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id):返回需要顯示內容的物件的控制程式碼。

EGLDisplay:EGL把這些不同平臺的顯示系統抽象為一個獨立的型別:EGLDisplay。

eglInitialize():初始化EGLDisplay。

EGLBoolean eglInitialize(EGLDisplay display, EGLint* majorVersion, EGLint* minorVersion);  
複製程式碼

EGLConfig:初始化EGLDisplay過後,要選擇一個合適的“繪圖表面”。

EGLBoolean eglChooseConfig(EGLDisplay display,  
                           const EGLint* attribs,    // 你想要的屬性事先定義到這個陣列裡  
                           EGLConfig* configs,       // 圖形系統將返回若干滿足條件的配置到該陣列  
                           EGLint maxConfigs,        // 上面陣列的容量  
                           EGLint* numConfigs);      // 圖形系統返回的可用的配置個數  
複製程式碼

eglCreateWindowSurface():為建立好的EGLDisplay建立視窗。

EGLSurface eglCreateWindowSurface(EGLDisplay display,  
                                  EGLConfig config,  
                                  EGLNativeWindowType window, // 在Windows上就是HWND型別  
                                  const EGLint* attribs);     // 此屬性表非彼屬性表 
複製程式碼

EGLSurface:EGLSurface 可以是由 EGL 分配的離屏緩衝區(稱為“pbuffer”),或由作業系統分配的視窗。EGL 視窗 Surface 通過 eglCreateWindowSurface() 呼叫被建立。該呼叫將“視窗物件”作為引數,在 Android 上,該物件可以是 SurfaceView、SurfaceTexture、SurfaceHolder 或 Surface,所有這些物件下面都有一個 BufferQueue。當您進行此呼叫時,EGL 將建立一個新的 EGLSurface 物件,並將其連線到視窗物件的 BufferQueue 的生產方介面。此後,渲染到該 EGLSurface 會導致一個緩衝區離開佇列、進行渲染,然後排隊等待消耗方使用。

eglCreateContext():eglCreateContext為當前渲染API(使用eglBindAPI設定)建立EGL渲染context,並返回context的控制程式碼。 context然後可以用於渲染到EGL繪圖表面。 如果eglCreateContext無法建立渲染context,則返回EGL_NO_CONTEXT。

EGLContext eglCreateContext(EGLDisplay display,
 	EGLConfig config,
 	EGLContext share_context,
 	EGLint const * attrib_list);
複製程式碼

display:指定EGL顯示連線。

config:指定定義可用於渲染context的幀緩衝區資源的EGL幀緩衝區配置。

share_context:指定用於共享資料的另一個EGL渲染context,由與context相對應的客戶端API定義。 資料也與share_context共享資料的所有其他context共享。 EGL_NO_CONTEXT表示不會發生共享。

attrib_list:為正在建立的context指定屬性和屬性值。 只能指定屬性EGL_CONTEXT_CLIENT_VERSION。

EGLContext:OpenGL ES 圖形上下文,它代表了OpenGL狀態機;如果沒有它,OpenGL指令就沒有執行的環境。

eglMakeCurrent():eglMakeCurrent將context繫結到當前渲染執行緒以及繪製和讀取表面。 繪製用於所有GL操作,除了讀取的任何畫素資料(glReadPixels,glCopyTexImage2D和glCopyTexSubImage2D),它取自讀取的幀緩衝區值。

如果呼叫執行緒已經有當前的渲染context,那麼該context將被重新整理並標記為不再是最新的。

當context首次生成時,viewport和scissor尺寸被設定為繪製表面的大小。 當context隨後變為當前時,viewport和scissor不會被修改。

EGLBoolean eglMakeCurrent(	EGLDisplay display,
 	EGLSurface draw,
 	EGLSurface read,
 	EGLContext context);
複製程式碼

6.頂點和片元shader初始化

#include <GLES2/gl2.h>
複製程式碼

頂點著色器

Android 基於ffmpeg開發簡易播放器 - EGL和OpenGLESGLES顯示YUV視訊

片元著色器

Android 基於ffmpeg開發簡易播放器 - EGL和OpenGLESGLES顯示YUV視訊

//頂點和片元shader初始化
//頂點shader初始化
GLint vsh = InitShader(vertexShader,GL_VERTEX_SHADER);
//片元yuv420 shader初始化
GLint fsh = InitShader(fragYUV420P,GL_FRAGMENT_SHADER);
複製程式碼
//頂點著色器glsl
#define GET_STR(x) #x
static const char *vertexShader = GET_STR(
    attribute vec4 aPosition; //頂點座標
    attribute vec2 aTexCoord; //材質頂點座標
    varying vec2 vTexCoord;   //輸出的材質座標
    void main(){
        vTexCoord = vec2(aTexCoord.x,1.0-aTexCoord.y);//以左上角為座標原點
        gl_Position = aPosition;
    }
);

//片元著色器,軟解碼和部分x86硬解碼
static const char *fragYUV420P = GET_STR(
    precision mediump float;    //精度
    varying vec2 vTexCoord;     //頂點著色器傳遞的座標
    uniform sampler2D yTexture; //輸入的材質(不透明灰度,單畫素)
    uniform sampler2D uTexture;
    uniform sampler2D vTexture;
    void main(){
        vec3 yuv;//承載輸入的資訊
        vec3 rgb;//承載輸出的資訊
        yuv.r = texture2D(yTexture,vTexCoord).r;//獲取指定座標的材質顏色
        yuv.g = texture2D(uTexture,vTexCoord).r - 0.5;
        yuv.b = texture2D(vTexture,vTexCoord).r - 0.5;
        rgb = mat3(1.0,     1.0,    1.0,
                   0.0,-0.39465,2.03211,
                   1.13983,-0.58060,0.0)*yuv;//指定的yuv轉換為rgb的公式
        //輸出畫素顏色
        gl_FragColor = vec4(rgb,1.0);
    }
);

GLint InitShader(const char *code,GLint type)
{
    //建立shader
    GLint sh = glCreateShader(type);
    if(sh == 0)
    {
        LOGD("glCreateShader %d failed!",type);
        return 0;
    }
    //載入shader
    glShaderSource(sh,
                   1,    //shader數量
                   &code, //shader程式碼
                   0);   //程式碼長度
    //編譯shader
    glCompileShader(sh);

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

7.建立渲染程式

//建立渲染程式
GLint program = glCreateProgram();
if(program == 0)
{
    LOGD("glCreateProgram failed!");
    return;
}
//渲染程式中加入著色器程式碼
glAttachShader(program,vsh);
glAttachShader(program,fsh);

//連結程式
glLinkProgram(program);
GLint status = 0;
glGetProgramiv(program,GL_LINK_STATUS,&status);
if(status != GL_TRUE)
{
    LOGD("glLinkProgram failed!");
    return;
}
glUseProgram(program);
LOGD("glLinkProgram success!");
複製程式碼

8.傳遞頂點資料

//加入三維頂點資料 兩個三角形組成正方形
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);
//傳遞頂點
glVertexAttribPointer(apos,3,GL_FLOAT,GL_FALSE,12,vers);
複製程式碼

9.傳遞材質資料

//加入材質座標資料
static float txts[] = {
        1.0f,0.0f , //右下
        0.0f,0.0f,
        1.0f,1.0f,
        0.0f,1.0f
};
GLuint atex = (GLuint)glGetAttribLocation(program,"aTexCoord");
glEnableVertexAttribArray(atex);
glVertexAttribPointer(atex,2,GL_FLOAT,GL_FALSE,8,txts);
複製程式碼

10.材質紋理初始化

int width = 424;
int height = 240;
    
//材質紋理初始化
//設定紋理層
glUniform1i( glGetUniformLocation(program,"yTexture"),0); //對於紋理第1層
glUniform1i( glGetUniformLocation(program,"uTexture"),1); //對於紋理第2層
glUniform1i( glGetUniformLocation(program,"vTexture"),2); //對於紋理第3層

//建立opengl紋理
GLuint texts[3] = {0};
//建立三個紋理
glGenTextures(3,texts);

//設定紋理屬性
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);
//設定紋理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
             0,           //細節基本 0預設
             GL_LUMINANCE,//gpu內部格式 亮度,灰度圖
             width,height, //拉昇到全屏
             0,             //邊框
             GL_LUMINANCE,//資料的畫素格式 亮度,灰度圖 要與上面一致
             GL_UNSIGNED_BYTE, //畫素的資料型別
             NULL                    //紋理的資料
);

//設定紋理屬性
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);
//設定紋理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
             0,           //細節基本 0預設
             GL_LUMINANCE,//gpu內部格式 亮度,灰度圖
             width/2,height/2, //拉昇到全屏
             0,             //邊框
             GL_LUMINANCE,//資料的畫素格式 亮度,灰度圖 要與上面一致
             GL_UNSIGNED_BYTE, //畫素的資料型別
             NULL                    //紋理的資料
);

//設定紋理屬性
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(GL_TEXTURE_2D,
             0,           //細節基本 0預設
             GL_LUMINANCE,//gpu內部格式 亮度,灰度圖
             width/2,height/2, //拉昇到全屏
             0,             //邊框
             GL_LUMINANCE,//資料的畫素格式 亮度,灰度圖 要與上面一致
             GL_UNSIGNED_BYTE, //畫素的資料型別
             NULL                    //紋理的資料
);
複製程式碼

11.紋理顯示

////紋理的修改和顯示
FILE *fp = fopen(url,"rb");
if(!fp)
{
    LOGD("open file %s failed!",url);
    return;
}
    
unsigned char *buf[3] = {0};//將顯示的材質存放到buf中
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++)
{
    //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(display,winsurface);
}
複製程式碼

相關文章