百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

超详细讲解IJKPlayer的播放器实战和源码分析(1)

bigegpt 2024-08-10 12:17 11 浏览

0.引言

关于本篇文章的学习,一定要先学习ffplay源码,对ffplay源码的整个流程要理解,才能够理解本篇文章,那就需要参考前面的文章。文章列表如下:

详细介绍ffplay命令(1)

FFmpeg的FFplay框架分析

超详细解析FFplay之音视频同步

超详细解析FFplay之音视频控制

超详细解析FFplay之数据读取线程

FFplay超详细数据结构分析

超详细解析FFplay之音视频SEEK操作

超详细解析FFplay之音视频解码线程

超详细解析FFplay之视频输出和尺寸变换模块

超详细解析FFplay之音视频输出模块


注意:本篇文章篇幅非常长,阅读起来需要花一些时间,接下来就开始认真学习IJKPlayer吧。


1.ijkplayer简述

本篇文章主要讲解ijkplayer重要源码分析(拉取的是最新的源码)和如何移植源码到qt的方法。ijkplayer是一个基于FFPlay源码的轻量级Android/iOS视频播放器,实现了跨平台的功能,API易于集成;编译配置可裁剪,?便控制安装包大小。接口和结构会直接借鉴IJKPlayer和ffplay。IJKPlayer和ffplay接口都是可以做到商用,可以使用这2种接口快速开发,如果做音视频的人很少,那可以直接基于这些接口开发。达到一个ijkplayer的效果。

ijkplayer源码地址:

https://gitee.com/mirrors/ijkplayer

界面如下:



2.ijkplayer目录结构

在功能的具体实现上,iOS和Android平台的差异主要表现在视频硬件解码以及?视频渲染??,两者实现的载体区别如下图所示:



ijkplayer源码主要由andoid、config、doc、extra、ijkmedia、ijkprof、ios、tools、xxx.sh、,ijkplayer源码的目录结构如下:



(1)android目录:android平台相关的上层接口封装以及平台相关方法,里面还有各种编译脚本相关,不同指令集的源码版本,如v7和v8等,还有一些patch相关的记录。具体细节部分,可以自行下载源码,然后阅读。

编译脚本:

(2)config目录:主要是编译ffmpeg使用的配置文件,如编译什么模块,如何编译HEVC等。如下图:


(3)extra目录:存放编译ijkplayer所需的依赖源文件,如ffmpeg、openssl、libyuv等。


(4)ijkmedia目录:这里面就是关于底层源码,包括jni,ffplay的源码。


(5)ijkprof目录:这个目录里面不太重要,内容不是很多。

(6)ios目录:ios平台上的上层接口封装及平台相关方法,同时也有一些编译脚本。


(7)tools:表示初始化项目工程脚本。


注意:上面目录的脚本也很多,每个脚本都有相应的功能,这些在做SDK时,也是值得我们参考和学习。


3.整体播放流程

read_thread线程负责解复用,,video_thread负责视频解码,audio_thread负责音频解码,ffplay的控制和显示是在一个线程,自己设计的这个播放器,控制和显示就不在同一个线程。控制就是在UI里面的子线程,如video_refresh_thread。


(1)把ijk的源码建立一个srcinsight的工程,可以很明显看到,ijk就是基于ffplay(特别是有些结构体,如packet队列,frame队列,都是照搬ffplay)做的优化和修改,在ff_ffplay_def.f里的结构体,下面这个FFPlayer的结构体是ijk重新又封装了,如下:

/* ffplayer */
struct IjkMediaMeta;
struct IJKFF_Pipeline;
typedef struct FFPlayer {
    const AVClass *av_class;

    /* ffplay context */
    VideoState *is;

    /* format/codec options */
    AVDictionary *format_opts;
    AVDictionary *codec_opts;
    AVDictionary *sws_dict;
    AVDictionary *player_opts;
    AVDictionary *swr_opts;
    AVDictionary *swr_preset_opts;

    /* ffplay options specified by the user */
#ifdef FFP_MERGE
    AVInputFormat *file_iformat;
#endif
    char *input_filename;
#ifdef FFP_MERGE
    const char *window_title;
    int fs_screen_width;
    int fs_screen_height;
    int default_width;
    int default_height;
    int screen_width;
    int screen_height;
#endif
    int audio_disable;
    int video_disable;
    int subtitle_disable;
    const char* wanted_stream_spec[AVMEDIA_TYPE_NB];
    int seek_by_bytes;
    int display_disable;
    int show_status;
    int av_sync_type;
    int64_t start_time;
    int64_t duration;
    int fast;
    int genpts;
    int lowres;
    int decoder_reorder_pts;
    int autoexit;
#ifdef FFP_MERGE
    int exit_on_keydown;
    int exit_on_mousedown;
#endif
    int loop;
    int framedrop;
    int64_t seek_at_start;
    int subtitle;
    int infinite_buffer;
    enum ShowMode show_mode;
    char *audio_codec_name;
    char *subtitle_codec_name;
    char *video_codec_name;
    double rdftspeed;
#ifdef FFP_MERGE
    int64_t cursor_last_shown;
    int cursor_hidden;
#endif
#if CONFIG_AVFILTER
    const char **vfilters_list;
    int nb_vfilters;
    char *afilters;
    char *vfilter0;
#endif
    int autorotate;
    int find_stream_info;
    unsigned sws_flags;

    /* current context */
#ifdef FFP_MERGE
    int is_full_screen;
#endif
    int64_t audio_callback_time;
#ifdef FFP_MERGE
    SDL_Surface *screen;
#endif

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

    char *video_codec_info;
    char *audio_codec_info;
    char *subtitle_codec_info;
    Uint32 overlay_format;

    int last_error;
    int prepared;
    int auto_resume;
    int error;
    int error_count;
    int start_on_prepared;
    int first_video_frame_rendered;
    int first_audio_frame_rendered;
    int sync_av_start;

    MessageQueue msg_queue;

    int64_t playable_duration_ms;

    int packet_buffering;
    int pictq_size;
    int max_fps;
    int startup_volume;

    int videotoolbox;
    int vtb_max_frame_width;
    int vtb_async;
    int vtb_wait_async;
    int vtb_handle_resolution_change;

    int mediacodec_all_videos;
    int mediacodec_avc;
    int mediacodec_hevc;
    int mediacodec_mpeg2;
    int mediacodec_mpeg4;
    int mediacodec_handle_resolution_change;
    int mediacodec_auto_rotate;

    int opensles;
    int soundtouch_enable;

    char *iformat_name;

    int no_time_adjust;
    double preset_5_1_center_mix_level;

    struct IjkMediaMeta *meta;

    SDL_SpeedSampler vfps_sampler;
    SDL_SpeedSampler vdps_sampler;

    /* filters */
    SDL_mutex  *vf_mutex;
    SDL_mutex  *af_mutex;
    int         vf_changed;
    int         af_changed;
    float       pf_playback_rate;
    int         pf_playback_rate_changed;
    float       pf_playback_volume;
    int         pf_playback_volume_changed;

    void               *inject_opaque;
    void               *ijkio_inject_opaque;
    FFStatistic         stat;
    FFDemuxCacheControl dcc;

    AVApplicationContext *app_ctx;
    IjkIOManagerContext *ijkio_manager_ctx;

    int enable_accurate_seek;
    int accurate_seek_timeout;
    int mediacodec_sync;
    int skip_calc_frame_rate;
    int get_frame_mode;
    GetImgInfo *get_img_info;
    int async_init_decoder;
    char *video_mime_type;
    char *mediacodec_default_name;
    int ijkmeta_delay_init;
    int render_wait_start;
    int is_manifest;
    LasPlayerStatistic las_player_statistic;
} FFPlayer;


(2)Packet队列数据结构如下。

typedef struct PacketQueue {
    MyAVPacketList *first_pkt, *last_pkt;
    int nb_packets;
    int size;
    int64_t duration;
    int abort_request;
    int serial;
    SDL_mutex *mutex;
    SDL_cond *cond;
    MyAVPacketList *recycle_pkt;
    int recycle_count;
    int alloc_count;

    int is_buffer_indicator;
} PacketQueue;

(3)Frame队列数据结构如下。

typedef struct FrameQueue {
    Frame queue[FRAME_QUEUE_SIZE];
    int rindex;
    int windex;
    int size;
 。。。
} FrameQueue;


注意:如果不懂前面ffplay的,可以看看前面的文章,这是理解ijk的基础。


(4)在ijk源码中,ff_ffplay.h是总体的一个头文件和对外提供接口的头文件。

//创建多个播放器
FFPlayer *ffp_create();
void      ffp_destroy(FFPlayer *ffp);
void      ffp_destroy_p(FFPlayer **pffp);
void      ffp_reset(FFPlayer *ffp);


(5)播放前设置参数的接口

/* set options before ffp_prepare_async_l() */

void     ffp_set_frame_at_time(FFPlayer *ffp, const char *path, int64_t start_time, int64_t end_time, int num, int definition);
void     *ffp_set_inject_opaque(FFPlayer *ffp, void *opaque);
void     *ffp_set_ijkio_inject_opaque(FFPlayer *ffp, void *opaque);
void      ffp_set_option(FFPlayer *ffp, int opt_category, const char *name, const char *value);
void      ffp_set_option_int(FFPlayer *ffp, int opt_category, const char *name, int64_t value);

int       ffp_get_video_codec_info(FFPlayer *ffp, char **codec_info);
int       ffp_get_audio_codec_info(FFPlayer *ffp, char **codec_info);


(6)播放控制

/* playback controll */
int       ffp_prepare_async_l(FFPlayer *ffp, const char *file_name);
int       ffp_start_from_l(FFPlayer *ffp, long msec);
int       ffp_start_l(FFPlayer *ffp);
int       ffp_pause_l(FFPlayer *ffp);
int       ffp_is_paused_l(FFPlayer *ffp);
int       ffp_stop_l(FFPlayer *ffp);
int       ffp_wait_stop_l(FFPlayer *ffp);

/* all in milliseconds */
int       ffp_seek_to_l(FFPlayer *ffp, long msec);
long      ffp_get_current_position_l(FFPlayer *ffp);
long      ffp_get_duration_l(FFPlayer *ffp);
long      ffp_get_playable_duration_l(FFPlayer *ffp);
void      ffp_set_loop(FFPlayer *ffp, int loop);
int       ffp_get_loop(FFPlayer *ffp);


(7)ff_ffmsg.h主要是一些回调信息,及时反馈的一些错误码信息。如下图:



4.移植重要源码到QT平台

添加顺序依次为

ff_ffplay_def.h

ff_fferror.h

ff_ffmsg.h

ff_ffplay.h:主要是对外提供接口。

(1)添加如下头文件

在qt项目下,新建头文件,



(2)创建目录在src下,名字为ff_ffplay_def.h,如下图所示:


并在ff_ffplay_def.h下添加如下的头文件,这些头文件也主要是来源于ffplay.c,添加如下:

#include <inttypes.h>
#include <math.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>

#include "libavutil/avstring.h"
#include "libavutil/eval.h"
#include "libavutil/mathematics.h"
#include "libavutil/pixdesc.h"
#include "libavutil/imgutils.h"
#include "libavutil/dict.h"
#include "libavutil/parseutils.h"
#include "libavutil/samplefmt.h"
#include "libavutil/avassert.h"
#include "libavutil/time.h"
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#include "libswscale/swscale.h"
#include "libavutil/opt.h"
#include "libavcodec/avfft.h"
#include "libswresample/swresample.h"


(3)添加ff_fferror.h,如下:



(4)包含头文件




(5)结构体IjkMediaPlayer包含了FFPlayer结构体,代码如下图所示:

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


(6)ijkplayer主要在移动端的解决方案,调用层次由java(是一个控件,显示画面,暂停,播放等,主要是业务相关)->ijkplayer_jni.c(jni)->ijkplayer.c->ff_ffplay.c。


(7)创建文件,qt接口,通过信号槽去触发,面向接口去编程,保证底层的ffplay.c的实现层不变。命名为ijkplayer_qt.cpp和ijkplayer_qt.h。这边就需要添加上ijkplayer.h、ijkplayer.c、ff_ffplay.c。


创建一个类,命名为ijkplayer_qt,如下图:




(8)在ijkplayer_qt.h添加如下源码:


ijkplayer_qt.cpp添加如下源码:



注意:现在主要是把架子搭起来。


(9)创建ijkplayer.h,如下图所示:



创建ijkplayer.cpp,如下图所示:


(10)创建ff_ffplay.c,如下图所示:



先实现一些初始化相关的工作,如下图所示:


在ff_ffplay.c里做的一些工作,如下图所示:



(11)添加消息队列接口ff_ffmsg.h,如下:




(12)添加config文件,如下:


(13)添加ff_ffinc.h文件,如下:



(14)消息队列的设计

qt播放按钮->IjkPlayerQt->IjkPlayer.cpp->ff_ffplay.c

创建一个结构体IjkMediaPlayer,这个结构体到时候要放在IjkPlayerQt使用。该结构体里面会包含FFPlayer,这样一种关联关系。同样要像IJK源码一样,实现一个loop的效果。

消息队列

初始化Init函数,创建player

信号槽

开启队列

设置资源

创建文件ijkplayer_internal.h。如下界面:


第二版编译完成。暂时没有报错。


5.Android初始化流程

播放的步骤:

设置播放源:ijkmp_set_data_source

启动播放:ijkmp_prepare_async

(1)创建播放器对象。函数IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)调用ijkmp_android_create(message_loop),message_loop作为回调函数被传入。代码如下图所示:

/**
 *  \brief Copy a portion of the texture to the current rendering target.
 *
 *  \param renderer The renderer which should copy parts of a texture.
 *  \param texture The source texture.
 *  \param srcrect   A pointer to the source rectangle, or NULL for the entire
 *                   texture.
 *  \param dstrect   A pointer to the destination rectangle, or NULL for the
 *                   entire rendering target.
 *
 *  \return 0 on success, or -1 on error
 */
extern DECLSPEC int SDLCALL SDL_RenderCopy(SDL_Renderer * renderer,
                                           SDL_Texture * texture,
                                           const SDL_Rect * srcrect,
                                           const SDL_Rect * dstrect);


(2)函数static int message_loop(void *arg)调用函数message_loop_n(env, mp),代码如下图所示:

static int message_loop(void *arg)
{
    MPTRACE("%s\n", __func__);

    JNIEnv *env = NULL;
    if (JNI_OK != SDL_JNI_SetupThreadEnv(&env)) {
        ALOGE("%s: SetupThreadEnv failed\n", __func__);
        return -1;
    }

    IjkMediaPlayer *mp = (IjkMediaPlayer*) arg;
    JNI_CHECK_GOTO(mp, env, NULL, "mpjni: native_message_loop: null mp", LABEL_RETURN);

    message_loop_n(env, mp);

LABEL_RETURN:
    ijkmp_dec_ref_p(&mp);

    MPTRACE("message_loop exit");
    return 0;
}


(3)ijkplayer_jni.c(jni)在这里有个循环控制入口,由这个函数进去。代码如下图所示:

SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255)

(4)函数message_loop_n(JNIEnv *env, IjkMediaPlayer *mp)调用这个函数ijkmp_get_msg(mp, &msg, 1)(涉及到消息队列这块)是非常重要。


6.播放流程

函数ijkmp_set_data_source从IDLE到INTIALIZED只是设置一个播放的url。函数ijkmp_prepare_async,从INTIALIZED到ASYNC_PREPING,是一个异步操作,做一些播放器的初始化工作。然后就到PREPARED状态,这时候表示初始化工作完成,然后调用ijkmp_start,到STARTED状态。播放流程的状态机如下图所示:



(1)播放开始流程,IjkMediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)->ijkmp_prepare_async(mp)->ijkmp_prepare_async_l(mp)->ffp_prepare_async_l(mp->ffplayer, mp->data_source)

 SDL_SetRenderTarget(renderer, NULL);

(2)播放接口

/**
 * \brief Set a texture as the current rendering target.
 *
 * \param renderer The renderer.
 * \param texture The targeted texture, which must be created with the SDL_TEXTUREACCESS_TARGET flag, or NULL for the default render target
 *
 * \return 0 on success, or -1 on error
 *
 *  \sa SDL_GetRenderTarget()
 */
extern DECLSPEC int SDLCALL SDL_SetRenderTarget(SDL_Renderer *renderer,
                                                SDL_Texture *texture);

(3)播放正真对接ffplay

static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
{
    assert(mp);

    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_IDLE);
 
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_ASYNC_PREPARING);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_PREPARED);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_STARTED);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_PAUSED);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_COMPLETED);
    
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_ERROR);
    MPST_RET_IF_EQ(mp->mp_state, MP_STATE_END);

  

    ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING);

    msg_queue_start(&mp->ffplayer->msg_queue);

    // released in msg_loop
    ijkmp_inc_ref(mp);
  //创建线程,回调之前用户创造的循环函数
    mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");
    // msg_thread is detached inside msg_loop
    // TODO: 9 release weak_thiz if pthread_create() failed;

    int retval = ffp_prepare_async_l(mp->ffplayer, mp->data_source);
    if (retval < 0) {
        ijkmp_change_state_l(mp, MP_STATE_ERROR);
        return retval;
    }

    return 0;
}

(4)通过这个接口,可以找到ffplay的函数了

SDL_RenderCopy(renderer, texture, NULL, NULL);


7.暂停流程

(1)函数IjkMediaPlayer_pause(JNIEnv *env, jobject thiz)->调用ijkmp_pause(mp)->ijkmp_pause_l(mp)->回调ffp_notify_msg1(mp->ffplayer, FFP_REQ_PAUSE)->msg_queue_put_simple3(&ffp->msg_queue, what, 0, 0)->msg_queue_put(q, &msg)->msg_queue_put_private(q, msg)->


/**
 *  \brief Update the screen with rendering performed.
 */
extern DECLSPEC void SDLCALL SDL_RenderPresent(SDL_Renderer * renderer);


(2)

SDL_RenderPresent(renderer);

(3)

/**
 *  \brief Update the screen with rendering performed.
 */
extern DECLSPEC void SDLCALL SDL_RenderPresent(SDL_Renderer * renderer);

(4)如这个暂停状态来说,函数ffp_pause_l(mp->ffplayer)->toggle_pause(ffp, 1),就到了FFplaye的源码,就会去调用这些关系。

int ffp_pause_l(FFPlayer *ffp)
{
    assert(ffp);
    VideoState *is = ffp->is;
    if (!is)
        return EIJK_NULL_IS_PTR;

    toggle_pause(ffp, 1);
    return 0;
}

(5)函数toggle_pause在ff_ffplay.c,源码如下。

static void toggle_pause(FFPlayer *ffp, int pause_on)
{
    SDL_LockMutex(ffp->is->play_mutex);
    toggle_pause_l(ffp, pause_on);
    SDL_UnlockMutex(ffp->is->play_mutex);
}

(6)

static void toggle_pause_l(FFPlayer *ffp, int pause_on)
{
    VideoState *is = ffp->is;
    if (is->pause_req && !pause_on) {
        set_clock(&is->vidclk, get_clock(&is->vidclk), is->vidclk.serial);
        set_clock(&is->audclk, get_clock(&is->audclk), is->audclk.serial);
    }
    is->pause_req = pause_on;
    ffp->auto_resume = !pause_on;
    stream_update_pause_l(ffp);
    is->step = 0;
}


暂停成功后,就会去调用函数ijkmp_change_state_l(mp, MP_STATE_PAUSED),去修改暂停状态。这个时候如果需要在java层去显示,那就需要反馈给java层去显示或通知用户。


8.消息通知

(1)使用消息通知的方式,做出相应的操作。

SDL_WaitEvent(&event);

(2)将消息放到消息队列里面去。

SDL_PushEvent(&event_q);

(3)

inline static int msg_queue_put(MessageQueue *q, AVMessage *msg)
{
    int ret;

    SDL_LockMutex(q->mutex);
    ret = msg_queue_put_private(q, msg);
    SDL_UnlockMutex(q->mutex);

    return ret;
}


(4)使用链表把消息串起来,并使用信号量SDL_CondSignal(q->cond)来通知其它线程去读取消息。

SDL_PumpEvents();


(5)由这个函数ijkmp_get_msg(IjkMediaPlayer *mp, AVMessage *msg, int block)去读取消息队列的消息(如暂停的消息),这个函数在前面也已经分析过了,即可以送到java层,也可以送到ffplay层,源码如下:

SDL_PeepEvents();


9.播放流程测试

在bin目录下,这个目录有这个日志文件:




10.IJK播放器时序

下面继续讲讲,如何从java层一直到ffplay的函数调用和分析。java层到ffplay的时序图如下:


(1)

(2)

ijkMediaPlayer.java

(3)进入底层


(4)对接native层的文件,这是一种静态注册的方法。



(5)对接的就是ijkplayer_jni.c(这一层全是对接的java的native方法)的函数static void IjkMediaPlayer_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject callback)


(6)再到了ijkplayer.c,调用这个函数int ijkmp_set_data_source(IjkMediaPlayer *mp, const char *url)。



(7)在ijkplayer.c文件中,代码风格是这样的,如ijkmp_set_data_source,主要是负责加锁,避免多线程的问题。那真正干活的就是ijkmp_set_data_source_I。保存地址,更改播放器状态。如下:


11.播放流程

(1)在文件IjkMediaPlayer.java中,函数prepareAsync()中,调用如下函数:

异步准备调用:


(2)在前面已经讲了设置好url的流程,就准备开始播了。如下调用关系:



(3)会调到文件Ijkplayer_jni.c的函数IjkMediaPlayer_prepareAsync,函数如下:



(4)在文件ijkplayer.c,函数IjkMediaPlayer_prepareAsync会调用ijkmp_prepare_async(IjkMediaPlayer *mp),函数如下:



(5)往java层和ffplay.c都是同一个队列。使用ijkmp_get_msg(xxx)往ff_ffplay.c里去处理。使用post_event是往java层去处理。是往一边发,还是两边发,使用标志continue_wait_next_msg。如果jni用不到,那这个消息就直接在消息队列中,被释放掉了。


(6)在文件ff_ffplay.c(这里面就是ffplay的那一套了),函数ijkmp_prepare_async_l会调用ffp_prepare_async_l(FFPlayer *ffp, const char *file_name),在该函数里面,最重要的就是调用stream_open(ffp, file_name, NULL),函数如下:


(7)ffp->is = is;这里保存了VideoState *is = stream_open(ffp, file_name, NULL)的参数,结构体也是一个接着一个管理,每个模块对接的都是只有一个总管。

(8)在函数stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat),就是各种初始化,如frame的队列初始化,packet队列初始化,时钟,音量,线程等初始化。这里还添加了支持,硬解的操作。


(9)在文件ff_ffplay.c,创建的读线程read_thread(void *arg),这里就可以去打开文件了。与前面分析的ffplay源码的文章如出一辙。传递的参数是FFPlayer *ffp(这个是后面自定义封装的)。


(10)在read_thread线程里,并把消息及时放到消息队列ffp_notify_msg1(ffp, FFP_MSG_OPEN_INPUT),发送给java层;在read_thread线程,正真打开码流的函数是stream_component_open(ffp, st_index[AVMEDIA_TYPE_SUBTITLE]),如下图:

(11)在该函数下初始化音视频,字幕的解码线程,如下:



注意:个人认为,虽然ffplay功能齐全,也比较稳定,但是这个框架,设计的不是很合理。

这段代码是调用硬件,ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);实际上硬解就是回调mediacode。在read_thread里,实际ijk后面还添加了码率统计。如有这样一行代码,如下:

ffp->stat.bit_rate = ic->bit_rate;


12.创建ffplayer对象

(1)创建ffplayer对象,是在文件ijkplayer.c的函数ijkmp_create(int (*msg_loop)(void*))。如下:


(2)真正创建是在ff_ffplay.c中,函数ffp_create(),如下调用:



(3)使用ffp_toggle_buffering(ffp, 1)先缓存,缓存够了,才播放。

SDL_Event

(4)

void ffp_toggle_buffering(FFPlayer *ffp, int start_buffering)
{
    SDL_LockMutex(ffp->is->play_mutex);
    ffp_toggle_buffering_l(ffp, start_buffering);
    SDL_UnlockMutex(ffp->is->play_mutex);
}

(5)

void ffp_toggle_buffering_l(FFPlayer *ffp, int buffering_on)
{
    if (!ffp->packet_buffering)
        return;

    VideoState *is = ffp->is;
    if (buffering_on && !is->buffering_on) {
        av_log(ffp, AV_LOG_DEBUG, "ffp_toggle_buffering_l: start\n");
        is->buffering_on = 1;
        stream_update_pause_l(ffp);
        if (is->seek_req) {
            is->seek_buffering = 1;
            ffp_notify_msg2(ffp, FFP_MSG_BUFFERING_START, 1);
        } else {
            ffp_notify_msg2(ffp, FFP_MSG_BUFFERING_START, 0);
        }
    } else if (!buffering_on && is->buffering_on){
        av_log(ffp, AV_LOG_DEBUG, "ffp_toggle_buffering_l: end\n");
        is->buffering_on = 0;
        stream_update_pause_l(ffp);
        if (is->seek_buffering) {
            is->seek_buffering = 0;
            ffp_notify_msg2(ffp, FFP_MSG_BUFFERING_END, 1);
        } else {
            ffp_notify_msg2(ffp, FFP_MSG_BUFFERING_END, 0);
        }
    }
}


(6)在read_thread线程函数,隔一段时间,会一直检测缓存是否有准备好。

event_q.type = FF_QUIT_EVENT;


13.总结

本篇文章通过大量的篇幅,理清了ijkPlayer从java层到ffplay的一个播放过程,对于基于ijkplayer的项目应用,具有十分重要的意义。除了理清整个播放过程,还把重要源码移植到qt平台,让qt能够吊起来,这也是具有十分好的实战学习。由于网上关于ijkplayer非常详细的文章,非常少,所以这篇文章也是花了很多心血总结,所以也是非常值得推荐给大家。欢迎关注,收藏,转发,分享。


后期关于项目知识,也会更新在微信公众号“记录世界 from antonio”,欢迎关注

转载记得注明出处,不要随意复制,黏贴,创作不易,支持原创

相关推荐

5分钟搭建公网https网页文件服务器,免费权威TLS证书

请关注本头条号,每天坚持更新原创干货技术文章。如需学习视频,请在微信搜索公众号“智传网优”直接开始自助视频学习前言本文主要讲解如何快速搭建一个https网页文件服务器,并免费申请权威机构颁发的tls证...

nginx负载均衡配置(nginx负载均衡配置两个程序副本)

Nginx是什么没有听过Nginx?那么一定听过它的“同行”Apache吧!Nginx同Apache一样都是一种WEB服务器。基于REST架构风格,以统一资源描述符(UniformResources...

19《Nginx 入门教程》Nginx综合实践

今天我们将基于Nginx完成两个比较有用的场景,但是用到的Nginx的配置非常简单。内部Yum源搭建内部Pip源搭建1.实验环境ceph1centos7.6内网ip:172.16....

Nginx性能调优与优化指南(nginx优化配置大全)

Nginx性能调优需要结合服务器硬件资源、业务场景和负载特征进行针对性优化。以下是一些关键优化方向和具体配置示例:一、Nginx配置优化1.进程与连接数优化nginxworker_process...

C++后端开发必须彻底搞懂Nginx,从原理到实战(高级篇)

本文为Nginx实操高级篇。通过配置Nginx配置文件,实现正向代理、反向代理、负载均衡、Nginx缓存、动静分离和高可用Nginx6种功能,并对Nginx的原理作进一步的解析。当需...

【Nginx】史上最全的Nginx配置详解

Nginx服务器配置中最频繁的部分,代理、缓存和日志定义等绝大多数功能和第三方模块的配置都在这里,http块又包括http全局块和server块。Nginx是非常重要的负载均衡中间件,被广泛应用于大型...

【Nginx】Nginx 4种常见配置实例(nginx基本配置与参数说明)

本文主要介绍nginx4种常见的配置实例。Nginx实现反向代理;Nginx实现负载均衡;Nginx实现动静分离;Nginx实现高可用集群;Nginx4种常见配置实例如下:一、Nginx反向代理配...

使用nginx+allure管理自动化测试报告

allure在自动化测试中经常用来生成漂亮的报告,但是网上及官网上给出的例子都仅仅是针对单个测试用例文件的形式介绍的,实际使用中,自动化测试往往需要包含不止一个产品或项目,本文介绍如何使用nginx+...

nginx配置文件详解(nginx配置文件详解高清版)

Nginx是一个强大的免费开源的HTTP服务器和反向代理服务器。在Web开发项目中,nginx常用作为静态文件服务器处理静态文件,并负责将动态请求转发至应用服务器(如Django,Flask,et...

SpringCloud Eureka-服务注册与发现

1.Eureka介绍1.1学习Eureka前的说明目前主流的服务注册&发现的组件是Nacos,但是Eureka作为老牌经典的服务注册&发现技术还是有必要学习一下,原因:(1)一些早期的分布式微服...

微服务 Spring Cloud 实战 Eureka+Gateway+Feign+Hystrix

前言我所在项目组刚接到一个微服务改造需求,技术选型为SpringCloud,具体需求是把部分项目使用SpringCloud技术进行重构。本篇文章中介绍了Eureka、Gateway、Fe...

深度剖析 Spring Cloud Eureka 底层实现原理

你作为一名互联网大厂后端技术开发人员,在构建分布式系统时,是不是常常为服务的注册与发现而头疼?你是否好奇,像SpringCloudEureka这样被广泛使用的组件,它的底层实现原理到底是怎样的...

热爱生活,喜欢折腾。(很热爱生活)

原文是stackoverflow的一则高票回答,原文链接可能之前也有人翻译过,但是刚好自己也有疑惑,所以搬运一下,个人水平有限所以可能翻译存在误差,欢迎指正(如侵删)。尽管classmethod和st...

GDB调试的高级技巧(详细描述gdb调试程序的全过程)

GDB是我们平时调试c/c++程序的利器,查起复杂的bug问题,比打印大法要好得多,但是也不得不说,gdb在默认情况下用起来并不是很好用,最近学习到几个高级点的技巧,分享下:一美化打印先上个例子...

Arduino 实例(二十三)Arduino 给Python 编译器发送信息

1首先Python需要安装Pyserial库,在命令提示符中输入pipintallpyserial若是遇到提示‘pip‘不是内部或外部命令,也不是可运行的程序或批处理文件,则需要设置环境变...