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

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

示例代码:

    // init playerIjkMediaPlayer.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() {@Overridepublic 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 LocalReferenceIJK_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_AVDEVICEavdevice_register_all();
#endif
#if CONFIG_AVFILTERavfilter_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 laterreturn;}// we shouldn't clear the target state, because somebody might have// called start() previouslyrelease(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 renderersfinal Context context = getContext();// REMOVED: SubtitleController// REMOVED: mAudioSessionmMediaPlayer.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不为nullif ((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;     //ffplayerint (*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_loopijkmp_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() {@Overridepublic void onPrepared(IMediaPlayer mp) {videoView.start();}});

ijkplayer初始化流程相关推荐

  1. ijkplayer 源码分析(1):初始化流程

    一.ijkplayer 初始化流程 本文是基于 A4ijkplayer 项目进行 ijkplayer 源码分析,该项目是将 ijkplayer 改成基于 CMake 编译,可导入 Android St ...

  2. Ijkplayer 初始化和prepare源码分析

    本文ijkplayer的版本是k0.8.8 一.Ijkplayer 初始化流程 1.Java层 – 在IjkVideoView中创建IMediaPlayer播放器接口 //创建Java层IMediaP ...

  3. Linux内核网络栈1.2.13-网卡设备的初始化流程

    参考资料 <<linux内核网络栈源代码情景分析>> 网卡设备的初始化 本文主要描述一下网卡设备的整个初始化的过程,该过程主要就是根据设备的硬件信息来获取与传输网络数据,注册相 ...

  4. 开机流程简介--init 处理系统初始化流程 (/etc/rc.d/rc.sysinit)

    init 处理系统初始化流程 (/etc/rc.d/rc.sysinit) 还记得上面提到 /etc/inittab 里头有这一句' si::sysinit:/etc/rc.d/rc.sysinit ...

  5. 关于Flutter初始化流程,我必须告诉你的是...

    作者:闲鱼技术-然道 1. 引言 最近在做性能优化的时候发现,在混合栈开发中,第一次启动Flutter页面的耗时总会是第二次启动Flutter页面耗时的两倍左右,这样给人感觉很不好.分析发现第一次启动 ...

  6. SpringMVC源码剖析(三)- DispatcherServlet的初始化流程

    我们启动web服务器,在浏览器中输入地址,就可以看到浏览器上输出我们写好的页面.为了更好的理解上面这个过程,你需要学习关于Servlet生命周期的三个阶段,就是所谓的"init-servic ...

  7. 深入分析 Flutter 初始化流程

    在调研 Flutter 动态化方案的时候,需要了解 Flutter 加载 dart 产物的流程,阅读了一部分源码,顺便也读了初始化相关的代码.于是梳理了一遍 Flutter 的初始化流程 flutte ...

  8. android6.0源码分析之Camera API2.0下的初始化流程分析

    1.Camera2初始化的应用层流程分析 Camera2的初始化流程与Camera1.0有所区别,本文将就Camera2的内置应用来分析Camera2.0的初始化过程.Camera2.0首先启动的是C ...

  9. caffe模型文件解析_深度学习 Caffe 初始化流程理解(数据流建立)

    深度学习 Caffe 初始化流程理解(数据流建立) 之前在简书的文章,搬迁过来 ^-^ 本文是作者原创,如有理解错误,恳请大家指出,如需引用,请注明出处. #Caffe FeatureMap数据流的建 ...

最新文章

  1. token验证失败_ASP.NET CORE WEBAPI JWT 带BEARER的TOKEN
  2. 比较全的字符串验证类,有人顶的话以后继续发
  3. (73)分析 KeInitializeApc ,了解 KAPC 的初始化
  4. 剑指offer--整数中1出现的次数
  5. html设置数组的方法,js改变原数组的方法有哪些?
  6. GAdminHttpd:图形化的 Apache 打点对象
  7. 阿里云,并不是比谁聪明,而是更早面临那些疼痛
  8. 人工智能数学基础:费马引理、罗尔定理、拉格朗日微分中值定理、柯西中值定理
  9. Vue组件传值、Vue、组件
  10. 下面关于在dos停止mysql_下面关于在DOS停止MySql的命令中,正确的是 (5.0分)_学小易找答案...
  11. mysql获取年月日周季度
  12. Axure RP8 下载、安装、破解、汉化一条龙服务
  13. 如何使用GAF进行地图应用的定制开发
  14. CodeForces - 835D Palindromic characteristics (dp)
  15. 使用selenium爬取猫眼,使用mitmproxy过美团检测
  16. 浅谈 缀点成线 问题
  17. 2021上海最新购房政策指南!买房、贷款、限购究竟有哪些变化?
  18. 2022-2028年中国草炭行业市场深度分析及发展趋向分析报告
  19. 2018最新破解pycharm安装过程(含注册码)
  20. Vue+Electron学习系列 (三) -- 自动更新

热门文章

  1. vmware虚拟机安装ghost版xp-黑屏错误及解决方法
  2. 21 CoCos Creator-设置
  3. js实现存储对象的数据结构hashTable和list
  4. 编译开源有限元软件calculix
  5. EOS Governor各类配置信息以及具体使用场景和含义
  6. 重启postgre_postgresql如何重启
  7. 河南家具分销系统开发|家具行业能用分销模式吗?
  8. 几点减几点怎么列算式_张宏伟:感悟减法算式模型的意义 破除一题一问的思维定势...
  9. 财务数据分析怎么做?财务公司应用方案
  10. java gc日志乱码_让bug无处藏身,Java 线上问题排查思路、常用工具