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