原本這個過程我是不大想寫初始化的過程,以爲網上已經有很多文章來分析了。可是在前面的整個分析過程當中,暴露了本身對一些問題理解還不夠透徹,所以有必要作一次。
首先是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函數,這裏進行了註冊。終於分析完了,總結起來就是各類初始化,協議的、解碼器的、網絡的、回調上層的。