ijkplayer初始化流程

ijkplayer 集成了三种播放器实现:

  • AndroidMediaPlayer:即安卓系统自带的播放器 MediaPlayer,基于 MediaCodec、AudioTrack 等安卓系统 API.
  • IjkExoMediaPlayer:即谷歌新推出的 ExoPlayer,同样是基于 MediaCodec、AudioTrack 等安卓系统 API,但相比 MediaPlayer 具有支持 DASH、高级 HLS、自定义扩展等优点。
  • IjkMediaPlayer:基于 FFmpeg 的 ffplay,集成了 MediaCodec 硬解码器、Opengl 渲染方式等。

示例代码:

    // init player
    IjkMediaPlayer.loadLibrariesOnce(null); // ①
    // 没有太大用处
    IjkMediaPlayer.native_profileBegin("libijkplayer.so");
    videoView.setVideoURI(Uri.parse("http://106.36.45.36/live.aishang.ctlcdn.com/00000110240001_1/encoder/1/playlist.m3u8")); // ②

    videoView.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(IMediaPlayer mp) {
            videoView.start();
        }
    });

我们在loadLibrariesOnce()中加载了我们编译出来的几个so文件,如下:

    libLoader.loadLibrary("ijkffmpeg");
    libLoader.loadLibrary("ijksdl");
    libLoader.loadLibrary("ijkplayer");

ffmpeg管协议和编解码,sdl管渲染显示,ijkplayer管理播放器。
libLoader可以看成System.loadLibrary(libName)
当Java层通过System.loadLibrary加载完JNI动态库后,紧接着会查找该库中一个叫JNI_OnLoad的函数。如果有,就调用它,而动态注册的工作就是在这里完成的(有一些初始化工作是可以在这里做的)

在ijkplayer_jni.c中找到了JNI_OnLoad()方法

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    // 指向JNI环境的指针
    JNIEnv* env = NULL;

    g_jvm = vm;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    // 如果条件为false,终止程序运行
    assert(env != NULL);

    pthread_mutex_init(&g_clazz.mutex, NULL );

    // FindClass returns LocalReference
    IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER);
    // 动态注册
    (*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) );

    // void ijkmp_global_init()
    // {
    //       ffp_global_init();
    // }
    ijkmp_global_init(); // ①
    ijkmp_global_set_inject_callback(inject_callback);

    FFmpegApi_global_init(env); // ②

    return JNI_VERSION_1_4;
}

RegisterNatives注册g_methods中的native方法,其中包括:

static JNINativeMethod g_methods[] = {
    {
        "_setDataSource",
        "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
        (void *) IjkMediaPlayer_setDataSourceAndHeaders
    },
    { "_setDataSourceFd",       "(I)V",     (void *) IjkMediaPlayer_setDataSourceFd },
    { "_setDataSource",         "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback },
   
    // ...
    { "_setVideoSurface",       "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },
    { "_prepareAsync",          "()V",      (void *) IjkMediaPlayer_prepareAsync },
    { "_start",                 "()V",      (void *) IjkMediaPlayer_start },
    { "_stop",                  "()V",      (void *) IjkMediaPlayer_stop },
    { "seekTo",                 "(J)V",     (void *) IjkMediaPlayer_seekTo },
    { "_pause",                 "()V",      (void *) IjkMediaPlayer_pause },
    { "isPlaying",              "()Z",      (void *) IjkMediaPlayer_isPlaying },
    { "getCurrentPosition",     "()J",      (void *) IjkMediaPlayer_getCurrentPosition },
    { "getDuration",            "()J",      (void *) IjkMediaPlayer_getDuration },
    { "_release",               "()V",      (void *) IjkMediaPlayer_release },
    { "_reset",                 "()V",      (void *) IjkMediaPlayer_reset },
    { "setVolume",              "(FF)V",    (void *) IjkMediaPlayer_setVolume },
    { "getAudioSessionId",      "()I",      (void *) IjkMediaPlayer_getAudioSessionId },
    { "native_init",            "()V",      (void *) IjkMediaPlayer_native_init },
    { "native_setup",           "(Ljava/lang/Object;)V", (void *) IjkMediaPlayer_native_setup },
    { "native_finalize",        "()V",      (void *) IjkMediaPlayer_native_finalize },

    // ...
    { "native_profileBegin",    "(Ljava/lang/String;)V",    (void *) IjkMediaPlayer_native_profileBegin },
    { "native_profileEnd",      "()V",                      (void *) IjkMediaPlayer_native_profileEnd },

    { "native_setLogLevel",     "(I)V",                     (void *) IjkMediaPlayer_native_setLogLevel },
    { "_setFrameAtTime",        "(Ljava/lang/String;JJII)V", (void *) IjkMediaPlayer_setFrameAtTime },
};

注册后,相当于java层声明为native方法的函数与JNI层有一个映射关系。

接着看调用了几个init方法:

  • ffp_global_init()
    主要是ffmpeg的初始化工作,注册解码器,然后注册协议
void ffp_global_init()
{
    if (g_ffmpeg_global_inited)
        return;

    /* register all codecs, demux and protocols */
    // 注册所有编译好的编解码器
    avcodec_register_all();
#if CONFIG_AVDEVICE
    avdevice_register_all();
#endif
#if CONFIG_AVFILTER
    avfilter_register_all();
#endif
    // 注册所有编译好的封装器
    av_register_all();

    // 基本上都是为了支持网络传输的协议注册
    ijkav_register_all();
    // 网络初始化
    avformat_network_init();

    av_lockmgr_register(lockmgr);
    av_log_set_callback(ffp_log_callback_brief);
    // AVPacket这个是存储压缩编码数据相关信息的结构体
    av_init_packet(&flush_pkt);
    flush_pkt.data = (uint8_t *)&flush_pkt;

    g_ffmpeg_global_inited = true;
}

  • FFmpegApi_global_init(env)
#define JNI_CLASS_FFMPEG_API "tv/danmaku/ijk/media/player/ffmpeg/FFmpegApi"
......
int FFmpegApi_global_init(JNIEnv *env)
{
    int ret = 0;

    IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_FFMPEG_API);
    (*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods));

    return ret;
}

// 就是base64的解码,指向ffmpeg的c函数
public class FFmpegApi {
    public static native String av_base64_encode(byte in[]);
}

IjkVideoView # setVideoURI()

private void setVideoURI(Uri uri, Map<String, String> headers) {
        mUri = uri;
        mHeaders = headers;
        mSeekWhenPrepared = 0;
        //
        openVideo();
        requestLayout();
        invalidate();
}

IjkVideoView # openVideo()

   @TargetApi(Build.VERSION_CODES.M)
    private void openVideo() {
        if (mUri == null || mSurfaceHolder == null) {
            // not ready for playback just yet, will try again later
            return;
        }
        // we shouldn't clear the target state, because somebody might have
        // called start() previously
        release(false);

        AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
        // 获取音频焦点
        am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

        try {
            // 参数就是IMediaPlayer类型
            mMediaPlayer = createPlayer(mSettings.getPlayer());

            // TODO: create SubtitleController in MediaPlayer, but we need
            // a context for the subtitle renderers
            final Context context = getContext();
            // REMOVED: SubtitleController

            // REMOVED: mAudioSession
            mMediaPlayer.setOnPreparedListener(mPreparedListener);
            // 省略一大堆setlistener...
            mCurrentBufferPercentage = 0;
            String scheme = mUri.getScheme();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                    mSettings.getUsingMediaDataSource() &&
                    (TextUtils.isEmpty(scheme) || scheme.equalsIgnoreCase("file"))) {
                IMediaDataSource dataSource = new FileMediaDataSource(new File(mUri.toString()));
                mMediaPlayer.setDataSource(dataSource);
            }  else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders);
            } else {
                mMediaPlayer.setDataSource(mUri.toString());
            }
            bindSurfaceHolder(mMediaPlayer, mSurfaceHolder);
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setScreenOnWhilePlaying(true);
            mPrepareStartTime = System.currentTimeMillis();
            mMediaPlayer.prepareAsync();
            if (mHudViewHolder != null)
                mHudViewHolder.setMediaPlayer(mMediaPlayer);

            // REMOVED: mPendingSubtitleTracks

            // we don't set the target state here either, but preserve the
            // target state that was there before.
            mCurrentState = STATE_PREPARING;
            attachMediaController();
        } catch (IOException ex) {
            Log.w(TAG, "Unable to open content: " + mUri, ex);
            mCurrentState = STATE_ERROR;
            mTargetState = STATE_ERROR;
            mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        } catch (IllegalArgumentException ex) {
            Log.w(TAG, "Unable to open content: " + mUri, ex);
            mCurrentState = STATE_ERROR;
            mTargetState = STATE_ERROR;
            mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        } finally {
            // REMOVED: mPendingSubtitleTracks.clear();
        }
    }

这里,做了以下事情:

  • 获得AudioManager
  • 通过setting获取player类型
  • 初始化IMediaPlayer(createPlayer()函数)
  • 调用setDataSource(String)
  • 屏幕常亮等
  • 设置MediaController
  • mMediaPlayer.prepareAsync()

初始化IMediaPlayer

这里讲的是IjkMediaPlayer(还有IjkExoMediaPlayer或者AndroidMediaPlayer)

    public IjkMediaPlayer(IjkLibLoader libLoader) {
        this.mWakeLock = null;
        this.initPlayer(libLoader);
    }

    private void initPlayer(IjkLibLoader libLoader) {
        loadLibrariesOnce(libLoader);
        //
        initNativeOnce();
        
        Looper looper;
        // 如果此线程的looper不为null
        if ((looper = Looper.myLooper()) != null) {
            this.mEventHandler = new IjkMediaPlayer.EventHandler(this, looper);
        // 否则,如果主线程的looper不为null
        } else if ((looper = Looper.getMainLooper()) != null) {
            this.mEventHandler = new IjkMediaPlayer.EventHandler(this, looper);
        } else {
            this.mEventHandler = null;
        }
        // 对应C语言的IjkMediaPlayer_native_setup()方法,也在ijkplayer_jni.c文件里面
        this.native_setup(new WeakReference(this));
    }

initNativeOnce()经过几次跳转调用了native方法的native_init函数,这里跳转到C代码里面,对应调用了IjkMediaPlayer_native_init(),接着初始化了一个Handler,后面在c层通过调用java的方法来post message

IjkMediaPlayer_native_setup()

static void
IjkMediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
    MPTRACE("%s\n", __func__);
    //
    IjkMediaPlayer *mp = ijkmp_android_create(message_loop);
    JNI_CHECK_GOTO(mp, env, "java/lang/OutOfMemoryError", "mpjni: native_setup: ijkmp_create() failed", LABEL_RETURN);

    jni_set_media_player(env, thiz, mp);
    ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
    ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
    ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, (*env)->NewGlobalRef(env, weak_this));

LABEL_RETURN:
    ijkmp_dec_ref_p(&mp);
}

IjkMediaPlayer的结构

struct IjkMediaPlayer {
    volatile int ref_count;
    pthread_mutex_t mutex;  //这是一个互斥锁,因为后面有很多多线程操作,同步少不了
    FFPlayer *ffplayer;     //ffplayer

    int (*msg_loop)(void*);    //msg的一个处理函数
    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;
};

ijkmp_android_create()

IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{
    IjkMediaPlayer *mp = ijkmp_create(msg_loop);
    if (!mp)
        goto fail;

    mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface(); //视频输出设备创建
    if (!mp->ffplayer->vout)
        goto fail;

    mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);    
    if (!mp->ffplayer->pipeline)
        goto fail;

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

    return mp;

fail:
    ijkmp_dec_ref_p(&mp);
    return NULL;
}

可见在create函数里给它指定了msg的处理函数和ffplayer,对ijkplayer结构体中的ffplayer进行设置。
在SDL_VoutAndroid_CreateForAndroidSurface()创建视频输出设备的时候,跟踪函数跳转发现,硬解用的mediacidec,软解用的ffmpeg。

setDataSource()

我们设置了视频源,在JNI层对应IjkMediaPlayer_setDataSourceAndHeaders(),然后代码跟踪到了ijkmp_set_data_source_l()

   //...
    freep((void**)&mp->data_source);
    mp->data_source = strdup(url);
   //...
    ijkmp_change_state_l(mp, MP_STATE_INITIALIZED);
   //...

在ijkmp_change_state_l里面封装了一个what为MP_STATE_INITIALIZED的AVMessage,最后把其放入ijkplayer->ffplayer->msg_queue中,它然后调用:

int SDL_CondSignal(SDL_cond *cond)
{
    assert(cond);
    if (!cond)
        return -1;

    return pthread_cond_signal(&cond->id);
}

cond->id其实是ijkplayer->ffplayer->msg_queue->cond->id,类型为pthread_cond_t。

pthread_cond_signal函数

在linux里面,pthread_cond_signal函数的作用是发送一个信号给另外一个正在阻塞等待状态的线程,使其脱离阻塞状态继续执行,当某个线程继续执行的时候发现msg_queue中有msg的时候会有相应操作。

prepareAsync()

它调用了IjkMediaPlayer_prepareAsync(),这个函数最终调用了ijkmp_prepare_async_1()

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

    //...
    assert(mp->data_source);
    //
    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;
}

ijkmp_change_state_l()向msg_queue发送一个状态
然后接下来就创建了一个msg_loop的消息循环线程,线程入口为ijkmp_msg_loop。这里其实就是上面放进msg_queue的message的处理线程。

最后,这里继续调用ffp_prepare_async_l()

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
//....
    // 一大波log信息
    av_log(NULL, AV_LOG_INFO, "===== versions =====\n");
    ffp_show_version_str(ffp, "FFmpeg",         av_version_info());
    ffp_show_version_int(ffp, "libavutil",      avutil_version());
    ffp_show_version_int(ffp, "libavcodec",     avcodec_version());
    ffp_show_version_int(ffp, "libavformat",    avformat_version());
    ffp_show_version_int(ffp, "libswscale",     swscale_version());
    ffp_show_version_int(ffp, "libswresample",  swresample_version());
    av_log(NULL, AV_LOG_INFO, "===== options =====\n");
    ffp_show_dict(ffp, "player-opts", ffp->player_opts);
    ffp_show_dict(ffp, "format-opts", ffp->format_opts);
    ffp_show_dict(ffp, "codec-opts ", ffp->codec_opts);
    ffp_show_dict(ffp, "sws-opts   ", ffp->sws_dict);
    ffp_show_dict(ffp, "swr-opts   ", ffp->swr_opts);
    av_log(NULL, AV_LOG_INFO, "===================\n");
//...
    //①
    VideoState *is = stream_open(ffp, file_name, NULL);
//...

    ffp->is = is;
    ffp->input_filename = av_strdup(file_name);
    return 0;
}

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;
//...
  
    // 
    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");
    //...
}

这里创建了两个线程,一个是以video_refresh_thread为入口的视频显示线程。还有一个是以read_thread为入口的网络数据或者本地文件的数据读取线程

在read_thread里面还向msg_queue发送了一个消息,那就是ffp_notify_msg1(ffp, FFP_MSG_PREPARED);,消息循环线程收到这个消息后,会调用java层的函数,向handler发送个MEDIA_PREPARED消息,然后在handler的handleMessage函数里面会处理这个消息

    case MEDIA_PREPARED:
                player.notifyOnPrepared();
                return;

最终会调用我们设置的

    videoView.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(IMediaPlayer mp) {
                videoView.start();
            }
        });

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值