音视频ijkplayer源码解析系列5--初始化源码拆解

还没有看过前置知识的同学可以先从下面的链接点过去:

音视频 ijkplayer 源码解析系列1--播放器介绍

音视频 ijkplayer源码解析系列2--如何解码图像

音视频ijkplayer源码解析系列3--解码流程

音视频ijkplayer源码解析系列4--ijkplayer里面如何使用SDL渲染

我们在前面的文章中分析了ijkplayer中的解码和渲染流程,但在ijkplayer播放器中还有一个十分重要的环节:初始化部分,初始化的逻辑十分的多,其中关键的就是音频和视频解码器的初始化。

在进行初始化代码解析之前,我们需要澄清下几个同名的类,细心的同学会发现,在ijkplayer播放器源码中出现了多个IjkMediaPlayer的类

  1. tv.danmaku.ijk.media.player.IjkMediaPlayer.java:这个类是java层播放器的类,也是所有使用ijkplayer播放器的关键类
  2. ijkmedia/ijkplayer/ijkplayer_internal.h#IjkMediaPlayer:这个类是c层的播放器类,c层使用播放器都需要借助这个类
  3. ijkmedia/ijkj4a/j4a/class/tv/danmaku/ijk/media/player/IjkMediaPlayer.c#J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer:这个类是jni层的中介类,它暴露相关成员变量的get和set属性,这些属性的地址就是java层的IjkMediaPlayer.java里的成员变量,存的就是c层创建的播放器类。经过上述的描述,它的作用也显而易见:它连接了java层的IjkMediaPlayer.java和c层的IjkMediaPlayer,让c层创建出来的播放器实例以成员变量的形式,都存在java层的播放器内,这样任何使用想使用这些成员量或者想通信的时候,都可以借助这个jni层的中介,同时也让这些成员变量变的更好管理。

简单介绍完了这几个同名类,我们开始正式讲解播放器的初始化相关细节喽:

在java层播放器的类名IjkMediaPlayer,在new这个IjkMediaPlayer对象的时候,其实走的就是它的构建方法

cpp 复制代码
public IjkMediaPlayer(IjkLibLoader libLoader) {
  initPlayer(libLoader);
}

private void initPlayer(IjkLibLoader libLoader) {
  loadLibrariesOnce(libLoader);
  initNativeOnce();

  Looper looper;
  if ((looper = Looper.myLooper()) != null) {
    mEventHandler = new EventHandler(this, looper);
  } else if ((looper = Looper.getMainLooper()) != null) {
    mEventHandler = new EventHandler(this, looper);
  } else {
    mEventHandler = null;
  }

  /*
         * Native setup requires a weak reference to our object. It's easier to
         * create it here than in C++.
         */
  native_setup(new WeakReference<IjkMediaPlayer>(this));
}

对应上述方法,其构建方法就是:

  1. 指定so库的加载:ijkffmpeg.soijksdl.soijkplayer.so
  2. 使用当前的Looper,创建EventHandler对象,这个EventHandler是播放器自定义的Handler类,用来传递播放器cpp层的消息至java层
  3. 执行native_setup(new WeakReference<IjkMediaPlayer>(this))方法,此时函数执行就来到了jni层的ijkplayer_jni#IjkMediaPlayer_native_setup方法

接下来,我们展开研究下第三步的函数IjkMediaPlayer_native_setup

cpp 复制代码
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_set_ijkio_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
    ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));

LABEL_RETURN:
    ijkmp_dec_ref_p(&mp);
}

从上面的源码中我们可以发现,其核心逻辑:

  1. 调用ijkmp_android_create创建IjkMediaPlayer实例,这个对象就是c层播放器的实例
  2. 保存最新创建的播放器实例
  3. 保存缓存与引用

接下来我们将上述的三个步骤层层拆解

1 IjkMediaPlayer实例创建

IjkMediaPlayer实例的创建是借助ijkmp_android_create函数实现的,接下来我们拆解下这个函数:

cpp 复制代码
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;
}

对应上述的代码,我们发现其最主要的工作就是:

  1. 调用ijkmp_create创建IjkMediaPlayer实例,其内部实现主要是:

    • 先是调用mallocz分配IjkMediaPlayer对象空间:

      cpp 复制代码
      IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
    • 然后创建FFPlayer对象,并保存到mp中:

      cpp 复制代码
      mp->ffplayer = ffp_create();
      // ffp_create其实关键逻辑就是mallocz创建FFPlayer的对象空间
    • 最后将java层传递过来的looper保存在播放器实例里:

      cpp 复制代码
      mp->msg_loop = msg_loop;
  2. 调用SDL_VoutAndroid_CreateForAndroidSurface创建SDL_Vout实例,然后保存在mp->ffplayer里面。那么SDL_Vout是个什么东西呢,细心的同学会发现还有个SDL_Aout对象,他是干什么用的呢

  3. 调用ffpipeline_create_from_android创建IJKFF_Pipeline对象并保存在mp->ffplayer里面。

  4. 最后调用ffpipeline_set_vout创建出来的SDL_Vout实例的弱引用保存在pipeline->opaque->weak_vout里面

经过了上述几个步骤,我们也就把IjkMediaPlayer对象基本创建完成了。

2 保存播放器实例

这里我们要引入一个新的类ijkplayer-arm64/src/main/jni/ijkmedia/ijkj4a/j4a/class/tv/danmaku/ijk/media/player/IjkMediaPlayer.c

java层也有一个同名的播放器类名tv/danmaku/ijk/media/player/IjkMediaPlayer.java,类名都是一样的,一个表示java层的播放器实例,而一个表示c层的播放器实例。后续这个类会频繁出现,我会标注清楚是c层的还是java层的,大家要注意分辨。

这个类的关键定义和实例化:

cpp 复制代码
typedef struct J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer {
    jclass id;

    jfieldID field_mNativeMediaPlayer;
    jfieldID field_mNativeMediaDataSource;
    jfieldID field_mNativeAndroidIO;
    jmethodID method_postEventFromNative;
    jmethodID method_onSelectCodec;
    jmethodID method_onNativeInvoke;
} J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer;
static J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer;

从cpp的写法来说,static J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer;这种写法就是全局变量,在类加载的时候就创建了这个全部变量。这个全部变量里面成员变量的作用为:

  1. id:对应java层的tv/danmaku/ijk/media/player/IjkMediaPlayer类,是通过jni的findclass方法获取到的jclass类
  2. java层的成员变量映射关系:
    1. field_mNativeMediaPlayer:则是对应的了java层IjkMediaPlayer类里的成员变量mNativeMediaPlayer
    2. field_mNativeMediaDataSource:则是对应的了java层IjkMediaPlayer类里的成员变量mNativeMediaDataSource
    3. field_mNativeAndroidIO:对应了ava层IjkMediaPlayer类里的成员变量mNativeAndroidIO
  3. java层的方法映射关系:
    1. method_postEventFromNative:对应了java层IjkMediaPlayer类里的方法postEventFromNative
    2. method_onSelectCodec:对应了java层IjkMediaPlayer类里的方法onSelectCodec
    3. method_onNativeInvoke:对应了java层IjkMediaPlayer类里的方法onNativeInvoke

我们在1.1 IjkMediaPlayer实例创建中描述了创建c层IjkMediaPlayer对象的完整过程,此时"保存播放器实例"到底是存到哪了呢,它的函数调用关系是这样的:

cpp 复制代码
jni_set_media_player(env, thiz, mp);
  ->J4AC_IjkMediaPlayer__mNativeMediaPlayer__set__catchAll(env, thiz, (intptr_t) mp);
		->J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer__mNativeMediaPlayer__set
      ->(*env)->SetLongField(env, thiz, class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.field_mNativeMediaPlayer, value);

我们可以发现在c层创建的ijkplayer_internal.h#IjkMediaPlayer对象存放到了field_mNativeMediaPlayer,其实也就是java层IjkMediaPlayer.java对象的mNativeMediaPlayer成员变量里

3 保存缓存与引用

在执行完上面两步以后,会将java层IjkMediaPlayer.java对象的弱引用保存在c层IjkMediaPlayer里面的这几个地方

  1. mp->weak_thiz,赋值代码:
cpp 复制代码
ijkmp_set_weak_thiz(mp, (*env)->NewGlobalRef(env, weak_this));
  1. mp->ffp->inject_opaque里,同时再进行ffmpeg环境的初始化,赋值代码:
cpp 复制代码
ijkmp_set_inject_opaque(mp, ijkmp_get_weak_thiz(mp));
  1. mp->ffp->ijkio_inject_opaque里,赋值代码:
cpp 复制代码
ijkmp_set_ijkio_inject_opaque(mp, ijkmp_get_weak_thiz(mp));

最后还会将编码器的回调mediacodec_select_callback和java层IjkMediaPlayer.java对象的弱引用都保存在mp->ffplayer->pipeline里,赋值代码如下:

cpp 复制代码
ijkmp_android_set_mediacodec_select_callback(mp, mediacodec_select_callback, ijkmp_get_weak_thiz(mp));

至此初始化的部分处理完成。

相关推荐
AJi1 天前
Android音视频框架探索(三):系统播放器MediaPlayer的创建流程
android·ffmpeg·音视频开发
音视频牛哥2 天前
Android平台如何高效移动RTMP|RTSP直播流的录像文件?
音视频开发·视频编码·直播
KeyFafa8885 天前
Android音视频学习(二) — FFmpeg常用的命令(查询命令)
音视频开发
哔哩哔哩技术10 天前
B站画质补完计划(4):SDR2HDR 让观感如临其境 Part.1
音视频开发
GetcharZp14 天前
Go语言实现屏幕截取+实时推流
后端·音视频开发
哔哩哔哩技术2 个月前
2025 B站春晚直播——流媒体技术助力直播体验提升与玩法创新
音视频开发
hepherd2 个月前
iOS - 音频: Core Audio - 播放
swift·音视频开发
音视频牛哥2 个月前
跨越技术藩篱,低延迟RTMP与RTSP播放器的战略意义
音视频开发·视频编码·直播
音视频牛哥2 个月前
流转时光,极致传输:大牛直播SDK跨平台RTMP播放模块的超低延迟之道
音视频开发·视频编码·直播
David凉宸2 个月前
视频融合 hls流如何对接
前端·音视频开发