美文网首页ijkplayer秘籍音视频开发
ijkplayer源码分析之surface与opengl关联初始

ijkplayer源码分析之surface与opengl关联初始

作者: ytxhao | 来源:发表于2017-07-10 13:44 被阅读472次

    ijkplayer源码分析之opengl与surface关联初始化

    =====================================================

    ijkplayer源码分析系列文章列表:

    ijkplayer源码分析之surface与opengl es关联初始化(一)

    ijkplayer源码分析之audio与opensl es初始化(二)

    ======================================================

    上层java代码

    • IjkMediaPlayer.java

    构造方法

    step 1: 
    IjkMediaPlayer()
        |--this(sLocalLibLoader);
            |--initPlayer(libLoader);
                |--loadLibrariesOnce(libLoader); //加载所需要的库
                |   |--libLoader.loadLibrary("ijkffmpeg");
                |   |--libLoader.loadLibrary("ijksdl");
                |   |--libLoader.loadLibrary("ijkplayer"); // step 2: 
                |--initNativeOnce(); 
                |   |--native_init(); //初始化 native 
                |
                | 
                |--native_setup(new WeakReference<IjkMediaPlayer>(this)); //step 3:
    
    
    

    底层c代码

    IjkMediaPlayer.java --> ijkplayer_jni.c

    step 2.5:
    native_init() --> IjkMediaPlayer_native_init
    //底层是个空实现
    IjkMediaPlayer_native_init(JNIEnv *env)
    {
        MPTRACE("%s\n", __func__);
    }
    
    step 3:
    native_setup --> IjkMediaPlayer_native_setup
    
    IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
    {
    
        IjkMediaPlayer *mp = ijkmp_android_create(message_loop); //创建一个 player 结构体
    
        jni_set_media_player(env, thiz, mp); //保存当前player结构体
        ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
        ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
        ijkmp_set_ijkio_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
        ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));
    
    LABEL_RETURN:
        ijkmp_dec_ref_p(&mp);
    }
    

    加载class的宏定义

    j4a_allclasses.loader.h  //头文件中包含加载class的宏定义
    
    J4A_LOAD_CLASS(java_nio_Buffer);
    J4A_LOAD_CLASS(java_nio_ByteBuffer);
    J4A_LOAD_CLASS(java_util_ArrayList);
    J4A_LOAD_CLASS(android_media_AudioTrack);
    J4A_LOAD_CLASS(android_media_MediaCodec);
    J4A_LOAD_CLASS(android_media_MediaFormat);
    J4A_LOAD_CLASS(android_media_PlaybackParams);
    J4A_LOAD_CLASS(android_os_Build);
    J4A_LOAD_CLASS(android_os_Bundle);
    J4A_LOAD_CLASS(tv_danmaku_ijk_media_player_misc_IMediaDataSource);
    J4A_LOAD_CLASS(tv_danmaku_ijk_media_player_misc_IIjkIOHttp);
    J4A_LOAD_CLASS(tv_danmaku_ijk_media_player_IjkMediaPlayer);
    
    
    #define J4A_LOAD_CLASS(class__)  --> J4A_loadClass__J4AC_##class__(env)
    --> J4A_loadClass__J4AC_IjkMediaPlayer
    or 
    --> J4A_loadClass__J4AC_MediaCodec
    

    class初始化流程

    step 2:
    Ijksdl_android_jni.c
    JNI_OnLoad(JavaVM *vm, void *reserved)
        |--J4A_LoadAll__catchAll(env);
    
    J4a_allclasses.c
    J4A_LoadAll__catchAll(JNIEnv *env)  //调用头文件中的宏定义函数
            |--#include "j4a/j4a_allclasses.loader.h"
                |--J4A_LOAD_CLASS(tv_danmaku_ijk_media_player_IjkMediaPlayer);
                    |--J4A_loadClass__J4AC_##class__(env);
                        |--J4A_loadClass__J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer(env)
                            |--class_id = class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.id;
                            |--name     = "mNativeMediaPlayer";
                            |--sign     = "J";
                            |--class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.field_mNativeMediaPlayer = J4A_GetFieldID__catchAll(env, class_id, name, sign);
    

    IjkMediaPlayer结构体

    struct IjkMediaPlayer {
        volatile int ref_count;
        pthread_mutex_t mutex;
        FFPlayer *ffplayer;
    
        int (*msg_loop)(void*);
        SDL_Thread *msg_thread;
        SDL_Thread _msg_thread;
    
        int mp_state;
        char *data_source;
        void *weak_thiz;
    
        int restart;
        int restart_from_beginning;
        int seek_req;
        long seek_msec;
    };
    
    

    step 3的创建player过程

    SDL_Class结构体变量

    typedef struct SDL_Class {
        const char *name;
    } SDL_Class;
    
    static SDL_Class g_class = {
        .name = "EGL",
    };
    
    static SDL_Class g_nativewindow_class = {
        .name = "ANativeWindow_Vout",
    };
    
    static SDL_Class g_pipeline_class = {
        .name = "ffpipeline_ffplay",
    };
    

    SDL_Vout结构体

    struct SDL_Vout {
        SDL_mutex *mutex;
    
        SDL_Class       *opaque_class;
        SDL_Vout_Opaque *opaque;
        SDL_VoutOverlay *(*create_overlay)(int width, int height, int frame_format, SDL_Vout *vout);
        void (*free_l)(SDL_Vout *vout);
        int (*display_overlay)(SDL_Vout *vout, SDL_VoutOverlay *overlay);
    
        Uint32 overlay_format;
    };
    

    SDL_Vout_Opaque结构体

    typedef struct SDL_Vout_Opaque {
        ANativeWindow   *native_window;
        SDL_AMediaCodec *acodec;
        int              null_native_window_warned; // reduce log for null window
        int              next_buffer_id;
    
        ISDL_Array       overlay_manager;
        ISDL_Array       overlay_pool;
    
        IJK_EGL         *egl;
    } SDL_Vout_Opaque;
    

    IJKFF_Pipeline结构体

    struct IJKFF_Pipeline {
        SDL_Class             *opaque_class;
        IJKFF_Pipeline_Opaque *opaque;
    
        void            (*func_destroy)             (IJKFF_Pipeline *pipeline);
        IJKFF_Pipenode *(*func_open_video_decoder)  (IJKFF_Pipeline *pipeline, FFPlayer *ffp);
        SDL_Aout       *(*func_open_audio_output)   (IJKFF_Pipeline *pipeline, FFPlayer *ffp);
    };
    
    IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
        |--IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
            |--IjkMediaPlayer *mp = ijkmp_create(msg_loop);
            |--mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface(); //创建surface用于视频输出
            |   |--SDL_VoutAndroid_CreateForANativeWindow();
            |       |--SDL_Vout_CreateInternal(size_t opaque_size)
            |       |   |--SDL_Vout *vout = (SDL_Vout*) calloc(1, sizeof(SDL_Vout));
            |       |
            |       |--SDL_Vout_Opaque *opaque = vout->opaque;
            |       |--opaque->native_window = NULL;
            |       |--opaque->egl = IJK_EGL_create();创建egl结构体
            |       |       |--IJK_EGL *egl = (IJK_EGL*) mallocz(sizeof(IJK_EGL));
            |       |       |--egl->opaque_class = &g_class;
            |       |       |--egl->opaque = mallocz(sizeof(IJK_EGL_Opaque));
            |       |
            |       |--vout->opaque_class    = &g_nativewindow_class;
            |       |--vout->create_overlay  = func_create_overlay;
            |       |--vout->free_l          = func_free_l;
            |       |--vout->display_overlay = func_display_overlay
            |       
            |
            |--mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer); //创建ffplay管线
            |   |--IJKFF_Pipeline *pipeline = ffpipeline_alloc(&g_pipeline_class, sizeof(IJKFF_Pipeline_Opaque));
            |   |--IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
            |   |--opaque->ffp                   = ffp;
            |   |--opaque->surface_mutex         = SDL_CreateMutex();
            |   |--opaque->left_volume           = 1.0f;
            |   |--opaque->right_volume          = 1.0f;
            |   |--pipeline->func_destroy            = func_destroy;
            |   |--pipeline->func_open_video_decoder = func_open_video_decoder;
            |   |--pipeline->func_open_audio_output  = func_open_audio_output;
            |
            |
            |--ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout); //将管线和视频输出关联
                |--IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
                |--opaque->weak_vout = vout;
    

    初始化总结

    step 1中进行IjkMediaPlayer实例的构造,构造过程中完成的step 2加载so并在加载过程中将底层的一些信息保存到IjkMediaPlayer实例中
    step 3将IjkMediaPlayer实例对象设置到底层IjkMediaPlayer结构体中,创建底层IjkMediaPlayer结构体。在创建结构体的同时设置message_loop回调函数实现将底层消息传递到上层的作用

    初始化后个结构体的关联图

    结构体关联图.jpg

    到此完成了IJKPlayer的初始化

    IjkMediaPlayer使用前的设置

    IjkMediaPlayer被封装到了IjkVideoView中,在IjkVideoView实例化的过程中会完成上面的初始化过程。同时在构造函数中会将surface设置到底层,下面是初始化图像渲染环境的代码分析

    以设置surfaceView为例:

    step 1:
    initVideoView()
        |--initRenders();
            |--setRender(mCurrentRender);
                |--renderView = new SurfaceRenderView(getContext());
                |   |--getHolder().addCallback(mSurfaceCallback); //当SurfaceRenderView被添加到屏幕显示时会回调 mSurfaceCallback 中的 surfaceCreated 方法,在 surfaceCreated 方法中又会调用注册到 renderView 中的 mSHCallback 实例的 onSurfaceCreated 方法
                |--setRenderView(renderView);
                    |--mRenderView.addRenderCallback(mSHCallback);
    
    mSHCallback = new IRenderView.IRenderCallback() //在 mSHCallback 的 onSurfaceCreated 中将 mSurfaceHolder 赋值
        |--onSurfaceCreated()
            |--mSurfaceHolder = holder;                 
    
    step 2:
    
    setVideoPath()
        |--setVideoURI()
            |--openVideo();
                |--mMediaPlayer = createPlayer(mSettings.getPlayer());
                |--bindSurfaceHolder(mMediaPlayer, mSurfaceHolder); //完成了 mSurfaceHolder 赋值后就将它和 mMediaPlayer 进行绑定
                    |--holder.bindToMediaPlayer(mp); 即 mSurfaceHolder.bindToMediaPlayer(mMediaPlayer)
                        |--InternalSurfaceHolder.bindToMediaPlayer()
                            |--mp.setDisplay(mSurfaceHolder); //即 mMediaPlayer.setDisplay(mSurfaceHolder)
                                |--surface = sh.getSurface();
                                |-- _setVideoSurface(surface); //native 方法设置surface到native
    

    go to native _setVideoSurface

    ijkplayer_jni.c

    IjkMediaPlayer_setVideoSurface
        |--ijkmp_android_set_surface //go to Ijkplayer_android.c
    

    Ijkplayer_android.c

    ijkmp_android_set_surface(JNIEnv *env, jobject thiz, jobject jsurface)
        |--ijkmp_android_set_surface_l(env, mp, android_surface);
            |--SDL_VoutAndroid_SetAndroidSurface(env, mp->ffplayer->vout, android_surface); //Ijksdl_vout_android_surface.c
            |--ffpipeline_set_surface(env, mp->ffplayer->pipeline, android_surface);
    

    Ijksdl_vout_android_surface.c

    SDL_VoutAndroid_SetAndroidSurface(env, mp->ffplayer->vout, android_surface)
        |--native_window = ANativeWindow_fromSurface(env, android_surface);
        |--SDL_VoutAndroid_SetNativeWindow(vout, native_window); //Ijksdl_vout_android_nativewindow.c
            |--SDL_VoutAndroid_SetNativeWindow_l(vout, native_window);
                |--IJK_EGL_terminate(opaque->egl);
                |--SDL_VoutAndroid_invalidateAllBuffers_l(vout);
                |--if (opaque->native_window)
                |       |--ANativeWindow_release(opaque->native_window);
                |--if (native_window)
                |       |--ANativeWindow_acquire(native_window);
                |--opaque->native_window = native_window;
    

    在上面的初始化的过程中就已经通过IjkMediaPlayer_native_setup函数对SDL_Vout结构中的回调函数,和egl信息进行了初始化。而IjkMediaPlayer_setVideoSurface则将native_window的地址保存到SDL_Vout->opaque->native_window中。到目前为止所有关于显示的信息都保存的了SDL_Vout结构体中.以便在ff_player.c文件中进行调用

    下面展示了调用过程:

    IjkMediaPlayer_prepareAsync //Ijkplayer_jni.c
        |--ijkmp_prepare_async
            |--ijkmp_prepare_async_l(IjkMediaPlayer *mp) //Ijkplayer.c
                |--ffp_prepare_async_l(FFPlayer *ffp, const char *file_name) //Ff_ffplay.c
                |--stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
                    |--SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");
                    |   |--video_refresh_thread(void *arg)
                    |       |--video_refresh(FFPlayer *opaque, double *remaining_time
                    |           |--video_display2(FFPlayer *ffp)
                    |               |--video_image_display2(FFPlayer *ffp)
                    |                   |--SDL_VoutDisplayYUVOverlay(ffp->vout, vp->bmp);
                    |                       |--vout->display_overlay(vout, overlay);  // vout->display_overlay = func_display_overlay;在SDL_VoutAndroid_CreateForANativeWindow中设置了回调函数
                    |                           |--func_display_overlay
                    |                               |--func_display_overlay_l(vout, overlay);
                    |                                   |--IJK_EGL_display(opaque->egl, native_window, overlay);
                    |                                   |   |--IJK_EGL_makeCurrent(egl, window)
                    |                                   |   |   |--eglMakeCurrent(egl->display, egl->surface, egl->surface, egl->context)
                    |                                   |   |--IJK_EGL_display_internal(egl, window, overlay);
                    |                                   |   |   |--IJK_EGL_prepareRenderer(egl, overlay)
                    |                                   |   |   |--IJK_GLES2_Renderer_renderOverlay(opaque->renderer, overlay)
                    |                                   |   |   |--eglSwapBuffers(egl->display, egl->surface);
                    |                                   |   |
                    |                                   |   |--eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
                    |                                   |--eglReleaseThread(); // FIXME: call at thread exit
                    |--SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read"); //创建视频文件读取线程
    

    流程图

    流程图.jpg

    相关文章

      网友评论

        本文标题:ijkplayer源码分析之surface与opengl关联初始

        本文链接:https://www.haomeiwen.com/subject/xpiyhxtx.html