ijkplayer源码分析 整体概述

前言

本系列是对ijkPlayer进行源码分析,本篇是对ijkPlayer的整体概述,分析了主要流程、主要结构体和初始化流程,为后面的文章打下基础。

主要流程

read_thread -> PacketQueue(AVPacket) -> FrameQueue(AVFrame) -> 渲染

对应关系 PacketQueue FrameQueue Clock 解码线程 渲染线程
视频 videoq pictq vidclk video_thread video_refresh_thread
音频 audioq sampq audclk audio_thread aout_thread
字幕 subtitleq subpq extclk subtitle_thread video_refresh_thread
  • 结构图
    在这里插入图片描述

  • 流程图
    在这里插入图片描述

主要的结构体

在native_setup方法中创建了IjkMediaPlayer、FFPlayer、IJKFF_Pipeline
在prepare阶段通过stream_open创建了VideoState。

IjkMediaPlayer

表示native层的Player,与Java层一对一绑定。作为Java到c的入口封装。

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;
};

FFPlayer

具体的播放器,internal player,包含编码、输出等;代码进入到ff_ffplay.c后,就都使用的是FFPlayer。持有VideoState。

typedef struct FFPlayer {
    
    
    VideoState *is;

    /* extra fields */
    SDL_Aout *aout;
    SDL_Vout *vout;
    struct IJKFF_Pipeline *pipeline;
    struct IJKFF_Pipenode *node_vdec;

    MessageQueue msg_queue;
}

VideoState

VideoState,在stream_open中被创建,表示播放过程中的所有状态。

typedef struct VideoState {
    
    
    Clock audclk;
    Clock vidclk;
    Clock extclk;

    FrameQueue pictq;
    FrameQueue subpq;
    FrameQueue sampq;

    Decoder auddec;
    Decoder viddec;
    Decoder subdec;

    PacketQueue audioq;
    PacketQueue subtitleq;
    PacketQueue videoq;

    int seek_req;
    double frame_timer;
    int abort_request;
    int force_refresh;
    int paused;

}

IJKFF_Pipeline

ffpipeline封装了视频解码器和音频解码器/输出,但实现都是通过函数指针调用别人的,有点像门面模式。

定义在ff_ffpipeline.h 和 ff_ffpipeline.c中

// ffpipeline_android.c中
typedef struct IJKFF_Pipeline_Opaque {
    
    
    FFPlayer      *ffp;
    SDL_mutex     *surface_mutex;
    jobject        jsurface;
    volatile bool  is_surface_need_reconfigure;

    bool         (*mediacodec_select_callback)(void *opaque, ijkmp_mediacodecinfo_context *mcc);
    void          *mediacodec_select_callback_opaque;

    SDL_Vout      *weak_vout;

    float          left_volume;
    float          right_volume;
} IJKFF_Pipeline_Opaque;

// 在ff_ffpipeline.h
typedef struct IJKFF_Pipeline_Opaque IJKFF_Pipeline_Opaque;
typedef struct IJKFF_Pipeline 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);
    IJKFF_Pipenode *(*func_init_video_decoder)  (IJKFF_Pipeline *pipeline, FFPlayer *ffp);
    int           (*func_config_video_decoder)  (IJKFF_Pipeline *pipeline, FFPlayer *ffp);
};

IJKFF_Pipeline *ffpipeline_alloc(SDL_Class *opaque_class, size_t opaque_size);
void ffpipeline_free(IJKFF_Pipeline *pipeline);
void ffpipeline_free_p(IJKFF_Pipeline **pipeline);

// 打开视频解码器
IJKFF_Pipenode *ffpipeline_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp);
// 打开音频解码器
SDL_Aout       *ffpipeline_open_audio_output(IJKFF_Pipeline *pipeline, FFPlayer *ffp);

// 异步初始化IJKFF_Pipenode
IJKFF_Pipenode* ffpipeline_init_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp); 

// 异步初始化视频解码器
int ffpipeline_config_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp);

serial字段

serial字段主要用于标记当前节点的序列号,ffplay中多处用到serial的概念,一般用于区分是否连续数据。当seek时会触发serial字段变化。
一般情况下新增节点与上一个节点的serial是一样的,但当队列中加入一个flush_pkt后,后续节点的serial会比之前大1。
serial是Packet、PacketQueue、Frame、Clock中有该值,会在各个地方都会判断。
Decoder里有pkt_serial,每次解码Packet时从pkt去取。

初始化流程

native_setup流程

  • new ijkMediaPlayer,构造函数调用native_setup
    ->ijkplayer_jni.c#IjkMediaPlayer_native_setup
    –>ijkplayer_android.c#ijkmp_android_create:创建IjkMediaPlayer、FFPlayer、IJKFF_Pipeline等
// ijkplayer_android.c
IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
    
    
    // 创建IjkMediaPlayer、FFPlayer
    IjkMediaPlayer *mp = ijkmp_create(msg_loop);  

    //  创建SDL_Vout
    mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();

    // 创建IJKFF_Pipeline
    mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);

    // 互相绑定
    ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);

    return mp;
}

IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*))
{
    
    
    IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
    mp->ffplayer = ffp_create();
    mp->msg_loop = msg_loop; // 赋值,后续prepare会启动线程调用该方法
    ijkmp_inc_ref(mp);
    pthread_mutex_init(&mp->mutex, NULL);

    return mp;
}

FFPlayer *ffp_create() {
    
    
    FFPlayer *ffp = (FFPlayer *) av_mallocz(sizeof(FFPlayer));

    msg_queue_init(&ffp->msg_queue);
    ffp->af_mutex = SDL_CreateMutex();
    ffp->vf_mutex = SDL_CreateMutex();
    ffp->stat_mutex = SDL_CreateMutex();

    ffp_reset_internal(ffp);
    ffp->av_class = &ffp_context_class;
    ffp->meta = ijkmeta_create();

    av_opt_set_defaults(ffp);

    return ffp;
}

prepare流程

  • VideoState
    在stream_open中被创建,封装了所有流程中需要的参数,大杂烩。

  • Java层prepareAsync
    -> ijkplayer_jni.c#IjkMediaPlayer_prepareAsync
    -> ijkplayer.c#ijkmp_prepare_async
    -> ijkplayer.c#ijkmp_prepare_async_l,创建ijkmp_msg_loop线程,启动msg_queue
    -> ff_ffplay.c#ffp_prepare_async_l,创建SDL_Aout,调用stream_open开始进入播放流程

static int ijkmp_prepare_async_l(IjkMediaPlayer *mp){
    
    
    ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING); // 改变状态
    msg_queue_start(&mp->ffplayer->msg_queue); // 启动message_queue
    SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
    ffp_prepare_async_l(mp->ffplayer, mp->data_source)
}

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name){
    
    
    VideoState *is = stream_open(ffp, file_name, NULL);
    ffp->is = is;
}

其它点

  • so加载,调用JNI_OnLoad方法,执行相关初始化操作
    libLoader.loadLibrary(“ijkffmpeg”); // ffmpeg
    libLoader.loadLibrary(“ijksdl”); // sdl
    libLoader.loadLibrary(“ijkplayer”); // player,文件依赖了ijkj4a,只打了这三个so

  • Java层调用:
    new ijkMediaPlayer,构造函数调用native_setup
    mMediaPlayer.setDataSource
    mMediaPlayer.setDisplay
    mMediaPlayer.prepareAsync
    mMediaPlayer.start

  • 消息队列,会在后面单独分析
    ijkplayer_jni.c#static int message_loop(void *arg)
    ijkplayer.c#prepare中创建线程,启动loop;
    消息队列是使用锁机制实现的looper那一套,等待唤醒实现的looper。

  • 代码结构
    image.png

参考
ijkPlayer主流程分析
ijkplayer框架深入剖析
解析 IJKPlayer
带问题重读ijkPlayer
知乎专栏:音视频技术

猜你喜欢

转载自blog.csdn.net/u014099894/article/details/112969853