原本这个过程我是不大想写初始化的过程,以为网上已经有很多文章来分析了。可是在前面的整个分析过程当中,暴露了本身对一些问题理解还不够透彻,所以有必要作一次。
首先是java层: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:app
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:less
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。
回来看这么多协议的注册,先看下这个宏:jvm
#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文件中:async
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的处理:tcp
#define IJK_REGISTER_DEMUXER(x) \ { \ extern AVInputFormat ijkff_##x##_demuxer; \ ijkav_register_input_format(&ijkff_##x##_demuxer); \ }
而后会定位到ijklivehook.c文件中的ide
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函数,这里进行了注册。终于分析完了,总结起来就是各类初始化,协议的、解码器的、网络的、回调上层的。