ijkplayer源码阅读

本文主要针对B站开源播放器IJKPlayer的部分源码阅读笔记,包括Java代码和C代码,涉及到部分FFmpeg和SDL的接口调用(笔记未经过仔细整理)。

哔哩哔哩 (゜-゜)つロ 干杯~-bilibili**

https://github.com/Bilibili/ijkplayer

## [Java] IjkMediaPlayer.java

实现了 android.media.MediaPlayer 类似的API.

内部主要是native实现和交互. native的mediaPlayer保存在field, mNativeMediaPlayer里.

主要的几个方法:

  • IjkMediaPlayer()
    • initPlayer()
      • native_init()
        • IjkMediaPlayer_native_init() // do nothing
      • native_setup()
        • IjkMediaPlayer_native_setup() -> @link native_setup()
  • setSurface()
    • native _setVideoSurface(surface);
      • SDL_VoutAndroid_SetAndroidSurface() // @link SDL_VoutAndroid_SetAndroidSurface()
      • ffpipeline_set_surface()
        • SDL_VoutAndroid_setAMediaCodec(pipeline->opaque->weak_vout, NULL)
  • setDataSource()
    • native _setDataSource()
      • ijkmp_set_data_source()
        • mp->data_source = strdup(url);
  • prepareAsync()
    • native IjkMediaPlayer_prepareAsync()
      • ijkmp_prepare_async() // @link ijkmp_prepare_async_l()
  • start()
    • native _start()
      • ijkmp_start()
        • ffp_notify_msg1(mp->ffplayer, FFP_REQ_START);
  • stop()
    • native _stop()
      • ijkmp_stop()
        • toggle_pause(ffp, 1);
        • msg_queue_abort(&ffp->msg_queue);
        • SDL_CondSignal(is->audio_accurate_seek_cond);
        • SDL_CondSignal(is->video_accurate_seek_cond);
  • native seekTo()
    • native ijkmp_seek_to_l()
      • ffp_notify_msg2(mp->ffplayer, FFP_REQ_SEEK, (int)msec);
  • native getCurrentPosition()
    • native ijkmp_get_current_position_l
      • ffp_get_current_position_l()
        • pos = fftime_to_milliseconds(is->seek_pos);
  • native getDuration()
    • native ijkmp_get_duration_l()
      • ffp_get_duration_l()
        • int64_t duration = fftime_to_milliseconds(is->ic->duration);
  • setOption
    • native _setOption()
      • ijkmp_set_option()
        • ffp_set_option(mp->ffplayer, opt_category, name, value);
          • av_dict_set
  • postEventFromNative() // @link postEventFromNative()
  • onNativeInvoke() // TODO
  • @link onSelectCodec()

具体native的方法bind在 ijkplayer_jni.c 里的 g_methods 里.

参数

参数为了如下几个类别:

  • OPT_CATEGORY_FORMAT 1
  • OPT_CATEGORY_CODEC 2
  • OPT_CATEGORY_SWS 3
  • OPT_CATEGORY_PLAYER 4 ffplay参数

IjkVideoView.java里设置的参数:

  • OPT_CATEGORY_PLAYER
    • mediacodec 0/1 硬解码
    • mediacodec-auto-rotate 0/1
    • mediacodec-handle-resolution-change 0/1
    • opensles 0/1 是否使用OpenSLES
    • overlay-format SDL_FCC_RV32(RGBX8888) / RGB565 / YV12
    • framedrop 1
    • start-on-prepared 0
  • OPT_CATEGORY_CODEC
    • skip_loop_filter 48
  • OPT_CATEGORY_FORMAT
    • http-detect-range-support 0
    • timeout 30 * 1000 * 1000
    • reconnect 1

### OPT_CATEGORY_PLAYER

源码:
ijkmedia\ijkplayer\ff_ffplay_options.h

  • start-on-prepared: automatically start playing on prepared
  • overlay-format: fourcc of overlay format
  • framedrop: drop frames when cpu is too slow
  • seek-at-start: set offset of player should be seeked
  • max-buffer-size max buffer size should be pre-read
  • min-frames minimal frames to stop pre-reading

android only options

  • mediacodec: MediaCodec: enable H264 (deprecated by 'mediacodec-avc')
  • mediacodec-auto-rotate: MediaCodec: auto rotate frame depending on meta
  • mediacodec-hevc: MediaCodec: enable HEVC
  • mediacodec-handle-resolution-change: MediaCodec: handle resolution change automatically
  • opensles: OpenSL ES: enable

[Native] ff_ffplay.c

@link ffp_set_inject_opaque()
// TODO

@link SDL_VoutAndroid_SetAndroidSurface()
ijkmedia\ijksdl\android\ijksdl_vout_android_surface.c

### ffp_prepare_async_l
@link ffp_prepare_async_l

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
    av_opt_set_dict(ffp, &ffp->player_opts);
    ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);


    VideoState *is = stream_open(ffp, file_name, NULL);  // _@link stream_open()_
}

@link stream_open()

static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{
    /* start video display */
    if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
        goto fail;
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
        goto fail;


    is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, **video_refresh_thread** , ffp, "ff_vout");
    is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");

    decoder_init(&is->viddec, NULL, &is->videoq, is->continue_read_thread);
}


static int **video_refresh_thread** (void *arg)
{
    FFPlayer *ffp = arg;
    VideoState *is = ffp->is;
    double remaining_time = 0.0;
    while (!is->abort_request) {
        if (remaining_time > 0.0)
            av_usleep((int)(int64_t)(remaining_time * 1000000.0));
            remaining_time = REFRESH_RATE;
        if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
            video_refresh(ffp, &remaining_time);
    }


    return 0;
}






read_thread

/* this thread gets the stream from the disk or the network */
static int read_thread(void *arg)
{
    AVPacket pkt1, *pkt = &pkt1;


    err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);
    ret = avformat_seek_file(ic, -1, INT64_MIN, timestamp, INT64_MAX, 0);


    st_index[AVMEDIA_TYPE_VIDEO] =
        av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
    st_index[AVMEDIA_TYPE_AUDIO] =
        av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, st_index[AVMEDIA_TYPE_AUDIO], st_index[AVMEDIA_TYPE_VIDEO], NULL, 0);
    st_index[AVMEDIA_TYPE_SUBTITLE] =
        av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE, st_index[AVMEDIA_TYPE_SUBTITLE], (st_index[AVMEDIA_TYPE_AUDIO] >= 0 ? st_index[AVMEDIA_TYPE_AUDIO] : st_index[AVMEDIA_TYPE_VIDEO]), NULL, 0)


    stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);
    stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]);
    stream_component_open(ffp, st_index[AVMEDIA_TYPE_SUBTITLE]);


    for (;;) {
        if (is->abort_request)
            break;
        if (is->seek_req) {
            ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
        }


        ret = av_read_frame(ic, pkt);

    }
}

native_setup()

@link native_setup()

[Native] ijkplayer.c

状态:

  • MP_STATE_IDLE
  • MP_STATE_INITIALIZED
  • MP_STATE_ASYNC_PREPARING
  • MP_STATE_PREPARED
  • MP_STATE_STARTED
  • MP_STATE_PAUSED
  • MP_STATE_COMPLETED
  • MP_STATE_STOPPED
  • MP_STATE_ERROR
  • MP_STATE_END

参数类型:

  • IJKMP_OPT_CATEGORY_FORMAT 1
  • IJKMP_OPT_CATEGORY_CODEC 2
  • IJKMP_OPT_CATEGORY_SWS 3
  • IJKMP_OPT_CATEGORY_PLAYER 4
  • IJKMP_OPT_CATEGORY_SWR

native_setup()

@link native_setup()

IjkMediaPlayer.java 构造器中 initPlayer() 函数中调用了

native_setup(weakRef(this));

IjkMediaPlayer_native_setup() at ijkplayer_jni.c

static void IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
    *mediaPlayer = ijkmp_android_create( **message_loop** )  //  _@link ijkmp_android_create()_
 _
_
    IjkMediaPlayer.setNativeMediaPlayer(mediaPlayer);


    ijkmp_set_inject_opaque(); // -> _@link ffp_set_inject_opaque()_
    ijkmp_set_ijkio_inject_opaque();
    ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));  // _@link onSelectCodec()_
}

@link ijkmp_android_create() at ijkplayer_android.c

IjkMediaPlayer *mp = ijkmp_create(msg_loop);  //  _@link ijkmp_create()_


mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();   //  _@link SDL_VoutAndroid_CreateForAndroidSurface()_
mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);


ffpipeline_set_vout(mp->ffplayer->pipeline, mp->ffplayer->vout);

@link ijkmp_create() at ijkplayer.c

IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
mp->ffplayer = ffp_create();  //  _@link ffp_create()_
mp->msg_loop = **msg_loop** ;  // _@link message_loop_n()_
return mp;

@link ffp_create() at ff_ffplay.c

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


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


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


    av_opt_set_defaults(ffp);

@link SDL_VoutAndroid_CreateForAndroidSurface() at ijkmedia\ijksdl\android\ijksdl_vout_android_surface.c
-> SDL_VoutAndroid_CreateForANativeWindow at ijkmedia\ijksdl\android\ijksdl_vout_android_nativewindow.c

SDL_Vout *SDL_VoutAndroid_CreateForANativeWindow()
{
    SDL_Vout *vout = SDL_Vout_CreateInternal(sizeof(SDL_Vout_Opaque));
    opaque->native_window = NULL;
    opaque->egl = IJK_EGL_create();
    // ...
}

@link message_loop_n

static void message_loop_n(JNIEnv *env, IjkMediaPlayer *mp) {


    while (1) {
        AVMessage msg;
        ijkmp_get_msg(mp, &msg, 1);

        switch(msg.what) {
            case ...:
                post_event();
                break;
        }
    }
}




inline static void post_event(JNIEnv *env, jobject weak_this, int what, int arg1, int arg2)
{
    J4AC_IjkMediaPlayer__postEventFromNative(env, weak_this, what, arg1, arg2, NULL);  // to java ->  _@link postEventFromNative_
}

ijkmp_prepare_async_l()

@link ijkmp_prepare_async_l()

static int ijkmp_prepare_async_l(IjkMediaPlayer *mp) {




    msg_queue_start(&mp->ffplayer->msg_queue);
    mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, **ijkmp_msg_loop** , mp, "ff_msg_loop");
    int retval = ffp_prepare_async_l(mp->ffplayer, mp->data_source);  // _@link ffp_prepare_async_l_


}


static int **ijkmp_msg_loop** (void *arg)
{
    IjkMediaPlayer *mp = arg;
    int ret = mp->msg_loop(arg);
    return ret;
}

Config

  • AVOption

ijkmedia\ijkplayer\ff_ffplay_options.h

  ijkExoMediaPlayer = null;
                    if (this.f1460g != null) {
                        ijkExoMediaPlayer = new IjkMediaPlayer(new C01825());
                        IjkMediaPlayer.native_setLogLevel(3);
                        ijkExoMediaPlayer.setOnNativeInvokeListener(this.f1449R);
                        if (this.f1474u.f1411d) {
                            ijkExoMediaPlayer.setOption(4, "mediacodec", 1);                      // "MediaCodec: enable H264 (deprecated by 'mediacodec-avc')"
                        } else {
                            ijkExoMediaPlayer.setOption(4, "mediacodec", 0);
                        }
                        ijkExoMediaPlayer.setOption(4, "mediacodec-auto-rotate", 0);               // "MediaCodec: auto rotate frame depending on meta"
                        ijkExoMediaPlayer.setOption(4, "mediacodec-handle-resolution-change", 0);    // "MediaCodec: handle resolution change automatically",
                        ijkExoMediaPlayer.setOption(4, "opensles", 0);                               // "OpenSL ES: enable",
                        ijkExoMediaPlayer.setOption(4, "overlay-format", 842225234);                // "fourcc of overlay format",
                        ijkExoMediaPlayer.setOption(4, "framedrop", 1);                              // "drop frames when cpu is too slow",
                        String str2 = "start-on-prepared";                                           // "automatically start playing on prepared",
                        if (this.f1454a) {
                            j = 1;
                        }
                        ijkExoMediaPlayer.setOption(4, str2, j);
                        ijkExoMediaPlayer.setOption(1, "http-detect-range-support", 0);
                        ijkExoMediaPlayer.setOption(2, "skip_loop_filter", 0);
                        ijkExoMediaPlayer.setOption(2, "skip_frame", 0);
                        ijkExoMediaPlayer.setOption(1, "timeout", (long) ((int) ((this.f1474u.f1410c * 1000.0f) * 1000.0f)));
                        ijkExoMediaPlayer.setOption(1, "reconnect", 1);
                        ijkExoMediaPlayer.setOption(1, "analyzeduration", 90000000);
                        if (this.f1474u.f1415h != null) {
                            str2 = null;
                            for (String str3 : this.f1474u.f1415h.keySet()) {
                                if (str2 == null) {
                                    str3 = String.format("%s: %s", new Object[]{str3, this.f1474u.f1415h.get(str3)});
                                } else {
                                    str3 = str2 + "\r\n" + String.format("%s: %s", new Object[]{str3, this.f1474u.f1415h.get(str3)});
                                }
                                str2 = str3;
                            }
                            ijkExoMediaPlayer.setOption(1, "headers", str2);
                        }
                        IjkMediaPlayer.native_setLogLevel(5);
                        if (this.f1474u.f1412e != null && this.f1433B.mo1065c(uri)) {
                            this.f1433B.mo1062a(this.f1474u.f1412e);
                            this.f1433B.mo1061a(this.f1474u.f1413f);
                            this.f1432A = this.f1433B.mo1063b(uri);
                            if (this.f1432A.mo1056b().endsWith("mp4")) {
                                ijkExoMediaPlayer.setOption(1, "cache_file_path", this.f1432A.mo1056b());
                                str3 = "ijkio:cache:ffio:" + this.f1460g.toString();
                            } else if (this.f1432A.mo1056b().endsWith(IjkMediaMeta.IJKM_KEY_M3U8)) {
                                ijkExoMediaPlayer.setOption(1, "cache_file_path", this.f1432A.mo1058c());
                                ijkExoMediaPlayer.setOption(1, "scheme_proxy", "ijkhttpcache");
                                ijkExoMediaPlayer.setOption(1, "protocol_whitelist", "tls,file,crypto,tcp,http,https,ijkhttpcache");
                                if (new File(this.f1432A.mo1056b()).exists()) {
                                    str3 = this.f1432A.mo1056b();
                                }
                            }
                            TXCLog.m416i(this.f1459f, "ijk media player");
                            break;
                        }
                    }
                    str3 = uri;
                    TXCLog.m416i(this.f1459f, "ijk media player");
static IJKFF_Pipenode *func_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp) {
    IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
    IJKFF_Pipenode *node = NULL;
    if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2)
        node = ffpipenode_create_video_decoder_from_android_mediacodec(ffp, pipeline, opaque->weak_vout);
    if (!node) {
        node = ffpipenode_create_video_decoder_from_ffplay(ffp);
    }
    return node
}




#define MEDIACODEC_MODULE_NAME "MediaCodec"








IJKFF_Pipenode *ffpipenode_create_video_decoder_from_android_mediacodec(FFPlayer *ffp, IJKFF_Pipeline *pipeline, SDL_Vout *vout)
{
    ALOGD("ffpipenode_create_video_decoder_from_android_mediacodec()\n")


    ffp_set_video_codec_info(ffp, MEDIACODEC_MODULE_NAME, opaque->mcc.codec_name);
    ffp->stat.vdec_type = FFP_PROPV_DECODER_MEDIACODEC;


}
#define AVCODEC_MODULE_NAME "avcodec"




IJKFF_Pipenode *ffpipenode_create_video_decoder_from_ffplay(FFPlayer *ffp) {




    ffp_set_video_codec_info(ffp, AVCODEC_MODULE_NAME, avcodec_get_name(ffp->is->viddec.avctx->codec_id));
    ffp->stat.vdec_type = FFP_PROPV_DECODER_AVCODEC;
}

NOTE ATTRIBUTES

Created Date: 2018-11-21 09:32:27
Last Evernote Update Date: 2020-05-23 07:35:27

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349