本来这个过程我是不大想写初始化的过程,觉得网上已经有不少文章来分析了。但是在前面的整个分析过程中,暴露了自己对一些问题理解还不够透彻,因此有必要做一次。
首先是java层:
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));
}
其实就2个事情,一个是loadLibrariesOnce,一个是initNativeOnce。前者的代码就不贴了,就是loadLibrary3个so,分别是ijkffmpeg、ijksdl和ijkplayer。ffmpeg管协议和编解码,sdl管渲染显示,ijkplayer管理播放器。每次调用loadLibrary都会走到每个so的JNI_OnLoad函数,也就是说这3个so的最开始初始化都在JNI_OnLoad这个函数内处理。回头我们再看;后者的initNativeOnce里面实际上走的是native_init。这个对应的是jni的函数IjkMediaPlayer_native_init。在ijkplayer_jni.c中:
static void
IjkMediaPlayer_native_init(JNIEnv *env)
{
MPTRACE("%s\n", __func__);
}
什么都没干,对吧。
回来,看看JNI_OnLoad都干了什么:
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
g_jvm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
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) );
ijkmp_global_init();
ijkmp_global_set_inject_callback(inject_callback);
FFmpegApi_global_init(env);
return JNI_VERSION_1_4;
}
前面都是通用的一些做法,主要是注册函数表,用来在java层能够调用c层的函数。然后是ijkmp_global_init,这个最后会走到ffp_global_init:
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);
av_init_packet(&flush_pkt);
flush_pkt.data = (uint8_t *)&flush_pkt;
g_ffmpeg_global_inited = true;
}
基本上以ffmpeg的初始化内容居多,av开头的应该都是。注册解码器,然后协议的注册。我们看ijkav_register_all:
void ijkav_register_all(void)
{
static int initialized;
if (initialized)
return;
initialized = 1;
av_register_all();
/* protocols */
av_log(NULL, AV_LOG_INFO, "===== custom modules begin =====\n");
#ifdef __ANDROID__
IJK_REGISTER_PROTOCOL(ijkmediadatasource);
#endif
IJK_REGISTER_PROTOCOL(async);
IJK_REGISTER_PROTOCOL(ijklongurl);
IJK_REGISTER_PROTOCOL(ijktcphook);
IJK_REGISTER_PROTOCOL(ijkhttphook);
IJK_REGISTER_PROTOCOL(ijksegment);
/* demuxers */
IJK_REGISTER_DEMUXER(ijklivehook);
av_log(NULL, AV_LOG_INFO, "===== custom modules end =====\n");
}
基本上都是为了支持网络传输的协议注册。然后是avformat_network_init,ffmpeg的网络初始化。最后到达ff_network_init,里面就是个WSAStartup。
回来看这么多协议的注册,先看下这个宏:
#define IJK_REGISTER_PROTOCOL(x) \
{ \
extern URLProtocol ijkimp_ff_##x##_protocol; \
int ijkav_register_##x##_protocol(URLProtocol *protocol, int protocol_size);\
ijkav_register_##x##_protocol(&ijkimp_ff_##x##_protocol, sizeof(URLProtocol)); \
}
URLProtocol结构是个关键。那么这个结构的填充靠什么呢?看宏的调用,找到extern后面的部分,搜索下,原来在不少文件里都有,例如ijkurlhook.c文件中:
URLProtocol ijkimp_ff_ijktcphook_protocol = {
.name = "ijktcphook",
.url_open2 = ijktcphook_open,
.url_read = ijkurlhook_read,
.url_write = ijkurlhook_write,
.url_close = ijkurlhook_close,
.priv_data_size = sizeof(Context),
.priv_data_class = &ijktcphook_context_class,
};
这里已经规定了打开和写入关闭等的函数。这下子与基础协议对应的各项操作算是找到了。我们来看看不一样的live的处理:
#define IJK_REGISTER_DEMUXER(x) \
{ \
extern AVInputFormat ijkff_##x##_demuxer; \
ijkav_register_input_format(&ijkff_##x##_demuxer); \
}
然后会定位到ijklivehook.c文件中的
AVInputFormat ijkff_ijklivehook_demuxer = {
.name = "ijklivehook",
.long_name = "Live Hook Controller",
.flags = AVFMT_NOFILE | AVFMT_TS_DISCONT,
.priv_data_size = sizeof(Context),
.read_probe = ijklivehook_probe,
.read_header2 = ijklivehook_read_header,
.read_packet = ijklivehook_read_packet,
.read_close = ijklivehook_read_close,
.priv_class = &ijklivehook_class,
};
往下看,以ijklivehook_read_header为例,可以看到内部有url的判断,区分rtmp和rtsp,这下子清楚了吧。
简单总结一下,就是通过URLProtocol这个结构来规范化所有的协议,名称和操作函数都在这里定义。
回到ffp_global_init,下面进行到了av_init_packet。这里插一下一个数据结构AVPacket。这个是存储压缩编码数据相关信息的结构体。
typedef struct AVPacket {
/**
* A reference to the reference-counted buffer where the packet data is
* stored.
* May be NULL, then the packet data is not reference-counted.
*/
AVBufferRef *buf;
/**
* Presentation timestamp in AVStream->time_base units; the time at which
* the decompressed packet will be presented to the user.
* Can be AV_NOPTS_VALUE if it is not stored in the file.
* pts MUST be larger or equal to dts as presentation cannot happen before
* decompression, unless one wants to view hex dumps. Some formats misuse
* the terms dts and pts/cts to mean something different. Such timestamps
* must be converted to true pts/dts before they are stored in AVPacket.
*/
int64_t pts;
/**
* Decompression timestamp in AVStream->time_base units; the time at which
* the packet is decompressed.
* Can be AV_NOPTS_VALUE if it is not stored in the file.
*/
int64_t dts;
uint8_t *data;
int size;
int stream_index;
/**
* A combination of AV_PKT_FLAG values
*/
int flags;
/**
* Additional packet data that can be provided by the container.
* Packet can contain several types of side information.
*/
AVPacketSideData *side_data;
int side_data_elems;
/**
* Duration of this packet in AVStream->time_base units, 0 if unknown.
* Equals next_pts - this_pts in presentation order.
*/
int64_t duration;
int64_t pos; ///< byte position in stream, -1 if unknown
#if FF_API_CONVERGENCE_DURATION
/**
* @deprecated Same as the duration field, but as int64_t. This was required
* for Matroska subtitles, whose duration values could overflow when the
* duration field was still an int.
*/
attribute_deprecated
int64_t convergence_duration;
#endif
} AVPacket;
看到了什么吗?pts,dts,data。显示时间戳,解码时间戳,数据。av_init_packet就是个简单填充,不贴代码了。回到JNI_OnLoad,然后进行的是ijkmp_global_set_inject_callback。
设置了一个回调,那么看看具体回调的约定吧:
static int
inject_callback(void *opaque, int what, void *data, size_t data_size)
{
JNIEnv *env = NULL;
jobject jbundle = NULL;
int ret = -1;
SDL_JNI_SetupThreadEnv(&env);
jobject weak_thiz = (jobject) opaque;
if (weak_thiz == NULL )
goto fail;
switch (what) {
case AVAPP_CTRL_WILL_HTTP_OPEN:
case AVAPP_CTRL_WILL_LIVE_OPEN:
case AVAPP_CTRL_WILL_CONCAT_SEGMENT_OPEN: {
AVAppIOControl *real_data = (AVAppIOControl *)data;
real_data->is_handled = 0;
jbundle = J4AC_Bundle__Bundle__catchAll(env);
if (!jbundle) {
ALOGE("%s: J4AC_Bundle__Bundle__catchAll failed for case %d\n", __func__, what);
goto fail;
}
J4AC_Bundle__putString__withCString__catchAll(env, jbundle, "url", real_data->url);
J4AC_Bundle__putInt__withCString__catchAll(env, jbundle, "segment_index", real_data->segment_index);
J4AC_Bundle__putInt__withCString__catchAll(env, jbundle, "retry_counter", real_data->retry_counter);
real_data->is_handled = J4AC_IjkMediaPlayer__onNativeInvoke(env, weak_thiz, what, jbundle);
if (J4A_ExceptionCheck__catchAll(env)) {
goto fail;
}
J4AC_Bundle__getString__withCString__asCBuffer(env, jbundle, "url", real_data->url, sizeof(real_data->url));
if (J4A_ExceptionCheck__catchAll(env)) {
goto fail;
}
ret = 0;
break;
}
case AVAPP_EVENT_WILL_HTTP_OPEN:
case AVAPP_EVENT_DID_HTTP_OPEN:
case AVAPP_EVENT_WILL_HTTP_SEEK:
case AVAPP_EVENT_DID_HTTP_SEEK: {
AVAppHttpEvent *real_data = (AVAppHttpEvent *) data;
jbundle = J4AC_Bundle__Bundle__catchAll(env);
if (!jbundle) {
ALOGE("%s: J4AC_Bundle__Bundle__catchAll failed for case %d\n", __func__, what);
goto fail;
}
J4AC_Bundle__putString__withCString__catchAll(env, jbundle, "url", real_data->url);
J4AC_Bundle__putLong__withCString__catchAll(env, jbundle, "offset", real_data->offset);
J4AC_Bundle__putInt__withCString__catchAll(env, jbundle, "error", real_data->error);
J4AC_Bundle__putInt__withCString__catchAll(env, jbundle, "http_code", real_data->http_code);
J4AC_IjkMediaPlayer__onNativeInvoke(env, weak_thiz, what, jbundle);
if (J4A_ExceptionCheck__catchAll(env))
goto fail;
ret = 0;
break;
}
case AVAPP_CTRL_DID_TCP_OPEN:
case AVAPP_CTRL_WILL_TCP_OPEN: {
AVAppTcpIOControl *real_data = (AVAppTcpIOControl *)data;
jbundle = J4AC_Bundle__Bundle__catchAll(env);
if (!jbundle) {
ALOGE("%s: J4AC_Bundle__Bundle__catchAll failed for case %d\n", __func__, what);
goto fail;
}
J4AC_Bundle__putInt__withCString__catchAll(env, jbundle, "error", real_data->error);
J4AC_Bundle__putInt__withCString__catchAll(env, jbundle, "family", real_data->family);
J4AC_Bundle__putString__withCString__catchAll(env, jbundle, "ip", real_data->ip);
J4AC_Bundle__putInt__withCString__catchAll(env, jbundle, "port", real_data->port);
J4AC_Bundle__putInt__withCString__catchAll(env, jbundle, "fd", real_data->fd);
J4AC_IjkMediaPlayer__onNativeInvoke(env, weak_thiz, what, jbundle);
if (J4A_ExceptionCheck__catchAll(env))
goto fail;
ret = 0;
break;
}
default: {
ret = 0;
}
}
fail:
SDL_JNI_DeleteLocalRefP(env, &jbundle);
return ret;
}
简单找个函数看下:J4AC_IjkMediaPlayer__onNativeInvoke,在java层里找到了定义:
private OnNativeInvokeListener mOnNativeInvokeListener;
public void setOnNativeInvokeListener(OnNativeInvokeListener listener) {
mOnNativeInvokeListener = listener;
}
public interface OnNativeInvokeListener {
int CTRL_WILL_TCP_OPEN = 0x20001; // NO ARGS
int CTRL_DID_TCP_OPEN = 0x20002; // ARG_ERROR, ARG_FAMILIY, ARG_IP, ARG_PORT, ARG_FD
int CTRL_WILL_HTTP_OPEN = 0x20003; // ARG_URL, ARG_SEGMENT_INDEX, ARG_RETRY_COUNTER
int CTRL_WILL_LIVE_OPEN = 0x20005; // ARG_URL, ARG_RETRY_COUNTER
int CTRL_WILL_CONCAT_RESOLVE_SEGMENT = 0x20007; // ARG_URL, ARG_SEGMENT_INDEX, ARG_RETRY_COUNTER
int EVENT_WILL_HTTP_OPEN = 0x1; // ARG_URL
int EVENT_DID_HTTP_OPEN = 0x2; // ARG_URL, ARG_ERROR, ARG_HTTP_CODE
int EVENT_WILL_HTTP_SEEK = 0x3; // ARG_URL, ARG_OFFSET
int EVENT_DID_HTTP_SEEK = 0x4; // ARG_URL, ARG_OFFSET, ARG_ERROR, ARG_HTTP_CODE
String ARG_URL = "url";
String ARG_SEGMENT_INDEX = "segment_index";
String ARG_RETRY_COUNTER = "retry_counter";
String ARG_ERROR = "error";
String ARG_FAMILIY = "family";
String ARG_IP = "ip";
String ARG_PORT = "port";
String ARG_FD = "fd";
String ARG_OFFSET = "offset";
String ARG_HTTP_CODE = "http_code";
/*
* @return true if invoke is handled
* @throws Exception on any error
*/
boolean onNativeInvoke(int what, Bundle args);
}
@CalledByNative
private static boolean onNativeInvoke(Object weakThiz, int what, Bundle args) {
DebugLog.ifmt(TAG, "onNativeInvoke %d", what);
if (weakThiz == null || !(weakThiz instanceof WeakReference<?>))
throw new IllegalStateException("<null weakThiz>.onNativeInvoke()");
@SuppressWarnings("unchecked")
WeakReference<IjkMediaPlayer> weakPlayer = (WeakReference<IjkMediaPlayer>) weakThiz;
IjkMediaPlayer player = weakPlayer.get();
if (player == null)
throw new IllegalStateException("<null weakPlayer>.onNativeInvoke()");
OnNativeInvokeListener listener = player.mOnNativeInvokeListener;
if (listener != null && listener.onNativeInvoke(what, args))
return true;
switch (what) {
case OnNativeInvokeListener.CTRL_WILL_CONCAT_RESOLVE_SEGMENT: {
OnControlMessageListener onControlMessageListener = player.mOnControlMessageListener;
if (onControlMessageListener == null)
return false;
int segmentIndex = args.getInt(OnNativeInvokeListener.ARG_SEGMENT_INDEX, -1);
if (segmentIndex < 0)
throw new InvalidParameterException("onNativeInvoke(invalid segment index)");
String newUrl = onControlMessageListener.onControlResolveSegmentUrl(segmentIndex);
if (newUrl == null)
throw new RuntimeException(new IOException("onNativeInvoke() = <NULL newUrl>"));
args.putString(OnNativeInvokeListener.ARG_URL, newUrl);
return true;
}
default:
return false;
}
}
那么可以确定,这里是注册的回调,以便通知java层。好吧,回来继续JNI_OnLoad,就差FFmpegApi_global_init了:
#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;
}
按照定义,找到这个类,只有一句话:
public class FFmpegApi {
public static native String av_base64_encode(byte in[]);
}
其实就是个base64的解码,指向ffmpeg的c函数,这里进行了注册。
终于分析完了,总结起来就是各种初始化,协议的、解码器的、网络的、回调上层的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。