本文主要分析ijk中hook协议的实现流程和具体实现来进行分析。
先看一下ijk中ijkhttphook的使用:
断网自动重新连接,url前接上ijkhttphook:,如
String url = "ijkhttphook:http://videocdn.eebbk.net/01cc6382b142217dad89516a19a4b299.mp4";
然后设置自定义的OnNativeInvokeListener接口。
((IjkMediaPlayer)mediaPlayer).setOnNativeInvokeListener(new IjkMediaPlayer.OnNativeInvokeListener() {
@Override
public boolean onNativeInvoke(int i, Bundle bundle) {
return true;
}
});
下面分析断网重连等实现方式:
先看一下ijkmediaplayer.java中 OnNativeInvokeListener接口的实现,如果没有自定义OnNativeListenr接口的话,从下面代码中我们可以看到则默认返回true。
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, ARG_FILE_SIZE
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";
String ARG_FILE_SIZE = "file_size";
/*
* @return true if invoke is handled
* @throws Exception on any error
*/
boolean onNativeInvoke(int what, Bundle args);
}
//默认的实现方法,供native层调用
@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(".onNativeInvoke()");
@SuppressWarnings("unchecked")
WeakReference weakPlayer = (WeakReference) weakThiz;
IjkMediaPlayer player = weakPlayer.get();
if (player == null)
throw new IllegalStateException(".onNativeInvoke()");
//如果实现了自己的mOnNativeInvokeListener
OnNativeInvokeListener listener = player.mOnNativeInvokeListener;
//如果自定义的onNativeInvokeListenr返回值是true的话,直接返回true
if (listener != null && listener.onNativeInvoke(what, args))
return true;
//如果没有实现自定义OnNativeListenr接口的话,则执行下面流程
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() = "));
args.putString(OnNativeInvokeListener.ARG_URL, newUrl);
return true;
}
default:
return false;
}
}
那么这里是从哪里开始初始化的呢?我们看下ijkplayer_jni.c文件下函数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_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);
}
跟踪ijkmp_set_inject_opaque函数,我们可以看到func_on_app_event函数指针初始化的地方
void *ffp_set_inject_opaque(FFPlayer *ffp, void *opaque)
{
if (!ffp)
return NULL;
void *prev_weak_thiz = ffp->inject_opaque;
ffp->inject_opaque = opaque;
av_application_closep(&ffp->app_ctx);
av_application_open(&ffp->app_ctx, ffp);
ffp_set_option_int(ffp, FFP_OPT_CATEGORY_FORMAT, "ijkapplication", (int64_t)(intptr_t)ffp->app_ctx);
//这个函数指针在http和tcp协议中会大量调用这个函数,记住在这里初始化的
ffp->app_ctx->func_on_app_event = app_func_event;
return prev_weak_thiz;
}
我们看下app_func_event函数,从上面我们可以看到ffp->app_ctx->func_on_app_event函数指针就是指向app_func_event函数的,这个函数在http和tcp协议中我们会看到多次使用,先立个flag,明确下初始化这个函数指针的地方。
static int app_func_event(AVApplicationContext *h, int message ,void *data, size_t size)
{
if (!h || !h->opaque || !data)
return 0;
FFPlayer *ffp = (FFPlayer *)h->opaque;
//没有注入操作,直接返回0
if (!ffp->inject_opaque)
return 0;
if (message == AVAPP_EVENT_IO_TRAFFIC && sizeof(AVAppIOTraffic) == size) {
AVAppIOTraffic *event = (AVAppIOTraffic *)(intptr_t)data;
if (event->bytes > 0) {
ffp->stat.byte_count += event->bytes;
SDL_SpeedSampler2Add(&ffp->stat.tcp_read_sampler, event->bytes);
}
} else if (message == AVAPP_EVENT_ASYNC_STATISTIC && sizeof(AVAppAsyncStatistic) == size) {
AVAppAsyncStatistic *statistic = (AVAppAsyncStatistic *) (intptr_t)data;
ffp->stat.buf_backwards = statistic->buf_backwards;
ffp->stat.buf_forwards = statistic->buf_forwards;
ffp->stat.buf_capacity = statistic->buf_capacity;
}
//message不在上面的话,走inject_callback函数来处理
return inject_callback(ffp->inject_opaque, message , data, size);
}
我们看下inject_callback函数的实现,这里调回到了ijkplayer_jni.c文件中,这个函数主要用来通过bundle传递数据到java层以及从java层取值,如OnNativeInvoke接口的返回值赋值给is_handled变量。
// NOTE: support to be called from read_thread
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;//默认AVAppIOControl的is_handled为0
//bundle
jbundle = J4AC_Bundle__Bundle__catchAll(env);
if (!jbundle) {
ALOGE("%s: J4AC_Bundle__Bundle__catchAll failed for case %d\n", __func__, what);
goto fail;
}
//放数据到bundle中
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);
//调用ijkmediaplayer中onNativeInvoke方法,拿到返回值
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_Bundle__putLong__withCString__catchAll(env, jbundle, "file_size", real_data->filesize);
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;
}
分析完hook协议的实现流程后,我们简单梳理下流程,
ijkhttphook与ijktcphook的实现都在ijkurlhook.c文件中,下面我们开始分析ijkhttphook协议。
我们先看下打开协议的实现函数ijkhttphook_open。
static int ijkhttphook_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
{
Context *c = h->priv_data;
int ret = 0;
c->app_ctx = (AVApplicationContext *)(intptr_t)c->app_ctx_intptr;
c->scheme = "ijkhttphook:";
//判断协议是否是ijkhttphook开头
if (av_stristart(arg, "ijkhttphook:https:", NULL))
c->inner_scheme = "https:";
else
c->inner_scheme = "http:";
//arg就是url连接:ijkhttphook:http://videocdn.eebbk.net/01cc6382b142217dad89516a19a4b299.mp4
//初始化app_io_ctrl的size,retry_counter,url
ret = ijkurlhook_init(h, arg, flags, options);
if (ret)
goto fail;
//中断检查,发送vent_type:AVAPP_CTRL_WILL_HTTP_OPEN
ret = ijkurlhook_call_inject(h);
if (ret)
goto fail;
//建立连接
ret = ijkurlhook_reconnect(h, NULL);
//如果连接失败,不断尝试连接
while (ret) {
int inject_ret = 0;
switch (ret) {
case AVERROR_EXIT:
goto fail;
}
c->app_io_ctrl.retry_counter++;
inject_ret = ijkurlhook_call_inject(h);
if (inject_ret) {
ret = AVERROR_EXIT;
goto fail;
}
//当我们把java层的OnNativeInvoke返回false时,在ijkplayer_jni.c中的inject_callback函数中is_handled被置为0,也就会中断while循环,不会不断循环尝试重试了。
if (!c->app_io_ctrl.is_handled)
goto fail;
av_log(h, AV_LOG_INFO, "%s: will reconnect at start\n", __func__);
ret = ijkhttphook_reconnect_at(h, 0);
av_log(h, AV_LOG_INFO, "%s: did reconnect at start: %d\n", __func__, ret);
}
fail:
return ret;
}
下面我们分别看下ijkurlhook_init,ijkurlhook_call_inject,ijkurlhook_reconnect函数
ijkurlhook_init函数主要用于初始化AVAPPIOControl结构体的size,segment_index(干嘛的),retry_counter(重试次数),url等变量。
static int ijkurlhook_init(URLContext *h, const char *arg, int flags, AVDictionary **options)
{
Context *c = h->priv_data;
int ret = 0;
//c->scheme是ijkhttphook:
av_strstart(arg, c->scheme, &arg);
c->inner_flags = flags;
if (options)
av_dict_copy(&c->inner_options, *options, 0);
//设置ijkapplication和ijkinject-segment-index到options中
av_dict_set_int(&c->inner_options, "ijkapplication", c->app_ctx_intptr, 0);
av_dict_set_int(&c->inner_options, "ijkinject-segment-index", c->segment_index, 0);
c->app_io_ctrl.size = sizeof(c->app_io_ctrl);
c->app_io_ctrl.segment_index = c->segment_index;
c->app_io_ctrl.retry_counter = 0;
if (av_strstart(arg, c->inner_scheme, NULL)) {
snprintf(c->app_io_ctrl.url, sizeof(c->app_io_ctrl.url), "%s", arg);
} else {
snprintf(c->app_io_ctrl.url, sizeof(c->app_io_ctrl.url), "%s%s", c->inner_scheme, arg);
}
//c->app_io_ctrl.url 这里就是没有ijkhttphook的url链接,如http://videocdn.eebbk.net/01cc6382b142217dad89516a19a4b299.mp4
return ret;
}
ijkurlhook_call_inject函数用于判断is_handled以及is_url_changed,发送event_type:AVAPP_CTRL_WILL_HTTP_OPEN后,我们可以在ijkplayer_jni.c的inject_callback函数中看到is_handled被置为1,对于为什么加中断检查没看明白,也没看明白检查url是否改变校验的含义。
static int ijkurlhook_call_inject(URLContext *h)
{
Context *c = h->priv_data;
int ret = 0;
if (ff_check_interrupt(&h->interrupt_callback)) {//中断检查,手动退出
ret = AVERROR_EXIT;
goto fail;
}
if (c->app_ctx) {
AVAppIOControl control_data_backup = c->app_io_ctrl;//控制数据备份
c->app_io_ctrl.is_handled = 0;
c->app_io_ctrl.is_url_changed = 0;//url是否改变置0
ret = av_application_on_io_control(c->app_ctx, AVAPP_CTRL_WILL_HTTP_OPEN, &c->app_io_ctrl);
if (ret || !c->app_io_ctrl.url[0]) {
ret = AVERROR_EXIT;
goto fail;
}
if (!c->app_io_ctrl.is_url_changed && strcmp(control_data_backup.url, c->app_io_ctrl.url)) {
// force a url compare
c->app_io_ctrl.is_url_changed = 1;//url改变了
}
av_log(h, AV_LOG_INFO, "%s %s (%s)\n", h->prot->name, c->app_io_ctrl.url, c->app_io_ctrl.is_url_changed ? "changed" : "remain");
}
if (ff_check_interrupt(&h->interrupt_callback)) {//中断检查
ret = AVERROR_EXIT;
av_log(h, AV_LOG_ERROR, "%s %s (%s)\n", h->prot->name, c->app_io_ctrl.url, c->app_io_ctrl.is_url_changed ? "changed" : "remain");
goto fail;
}
fail:
return ret;
}
跟踪一下av_application_on_io_control/application.c/libavformat函数可以看到,这个application.c文件也是哔哩哔哩自己添加到ffmpeg中的,这个函数主要调用AVApplicationContext的func_on_app_event函数指针,在第二章中,我已经对这个函数指针的初始化赋值的地方进行了说明,也就是调用app_func_event/ff_play.c函数,app_func_event再调用ijkplayer_jni.c中的inject_callback函数来与java层进行数据交换和处理。
int av_application_on_io_control(AVApplicationContext *h, int event_type, AVAppIOControl *control)
{
if (h && h->func_on_app_event)
return h->func_on_app_event(h, event_type, (void *)control, sizeof(AVAppIOControl));
return 0;
}
在open协议的时候,建立连接
static int ijkurlhook_reconnect(URLContext *h, AVDictionary *extra)
{
Context *c = h->priv_data;
int ret = 0;
URLContext *new_url = NULL;
AVDictionary *inner_options = NULL;
c->test_fail_point_next += c->test_fail_point;
assert(c->inner_options);
//拷贝options
av_dict_copy(&inner_options, c->inner_options, 0);
if (extra)
av_dict_copy(&inner_options, extra, 0);
//打开协议
ret = ffurl_open_whitelist(&new_url,
c->app_io_ctrl.url,
c->inner_flags,
&h->interrupt_callback,
&inner_options,
h->protocol_whitelist,
h->protocol_blacklist,
h);
if (ret)
goto fail;
ffurl_closep(&c->inner);
c->inner = new_url;//获取ijkhttphook下一层协议的URLContext,如http的URLContext
h->is_streamed = c->inner->is_streamed;
//赋值物理地址
c->logical_pos = ffurl_seek(c->inner, 0, SEEK_CUR);
//可以streamed的话,物理地址设置为-1
if (c->inner->is_streamed)
c->logical_size = -1;
else
c->logical_size = ffurl_seek(c->inner, 0, AVSEEK_SIZE);
c->io_error = 0;
fail:
av_dict_free(&inner_options);
return ret;
}
如果连接失败,open函数会不断尝试建立连接。
ijkhttphook_read调用ijkurlhook_read读取数据,如果读取失败不断尝试重连
static int ijkhttphook_read(URLContext *h, unsigned char *buf, int size)
{
Context *c = h->priv_data;
int ret = 0;
c->app_io_ctrl.retry_counter = 0;
ret = ijkurlhook_read(h, buf, size);
//读取失败,不断尝试重新建立连接读取数据
while (ret < 0 && !h->is_streamed && c->logical_pos < c->logical_size) {
switch (ret) {//如果AVERROR_EXIT直接退出
case AVERROR_EXIT:
goto fail;
}
//重新尝试次数++
c->app_io_ctrl.retry_counter++;
//中断检查以及发送event_type:AVAPP_CTRL_WILL_HTTP_OPEN
ret = ijkurlhook_call_inject(h);
if (ret)
goto fail;
//open的时候发送event_type在ijkplayer_jni.c中已赋值is_handled为1
if (!c->app_io_ctrl.is_handled)
goto fail;
av_log(h, AV_LOG_INFO, "%s: will reconnect(%d) at %"PRId64"\n", __func__, c->app_io_ctrl.retry_counter, c->logical_pos);
//重连
ret = ijkhttphook_reconnect_at(h, c->logical_pos);
av_log(h, AV_LOG_INFO, "%s: did reconnect(%d) at %"PRId64": %d\n", __func__, c->app_io_ctrl.retry_counter, c->logical_pos, ret);
if (ret < 0)
continue;
//读数据,如果读取成功,跳出while循环,继续开始读数据
ret = ijkurlhook_read(h, buf, size);
}
fail:
if (ret <= 0) {
c->io_error = ret;
}
return ret;
}
跟踪下ijkhttphook_reconnect_at函数,这个函数就是封装了ijkurlhook_reconnect重连函数,并做了清除dns缓存以及offset保存的操作。
static int ijkhttphook_reconnect_at(URLContext *h, int64_t offset)
{
int ret = 0;
AVDictionary *extra_opts = NULL;
av_dict_set_int(&extra_opts, "offset", offset, 0);
av_dict_set_int(&extra_opts, "dns_cache_clear", 1, 0);//清除dns
ret = ijkurlhook_reconnect(h, extra_opts);//重连
av_dict_free(&extra_opts);
return ret;
}
看下ijkurlhook_read函数,直接调用ffurl_read/avio.c函数来读取数据。
static int ijkurlhook_read(URLContext *h, unsigned char *buf, int size)
{
Context *c = h->priv_data;
int ret = 0;
if (c->io_error < 0)
return c->io_error;
if (c->test_fail_point_next > 0 && c->logical_pos >= c->test_fail_point_next) {
av_log(h, AV_LOG_ERROR, "test fail point:%"PRId64"\n", c->test_fail_point_next);
c->io_error = AVERROR(EIO);
return AVERROR(EIO);
}
ret = ffurl_read(c->inner, buf, size);
//读取到数据,物理地址加上读取到的数据
if (ret > 0)
c->logical_pos += ret;
else
c->io_error = ret;
return ret;
}
static int64_t ijkhttphook_seek(URLContext *h, int64_t pos, int whence)
{
Context *c = h->priv_data;
int ret = 0;
int64_t seek_ret = -1;
if (whence == AVSEEK_SIZE)
return c->logical_size;
else if ((whence == SEEK_CUR && pos == 0) ||
(whence == SEEK_SET && pos == c->logical_pos))
return c->logical_pos;
else if ((c->logical_size < 0 && whence == SEEK_END) || h->is_streamed)
return AVERROR(ENOSYS);
c->app_io_ctrl.retry_counter = 0;
ret = ijkurlhook_call_inject(h);
if (ret) {
ret = AVERROR_EXIT;
goto fail;
}
seek_ret = ijkhttphook_reseek_at(h, pos, whence, c->app_io_ctrl.is_url_changed);
while (seek_ret < 0) {
switch (seek_ret) {
case AVERROR_EXIT:
case AVERROR_EOF:
goto fail;
}
c->app_io_ctrl.retry_counter++;
ret = ijkurlhook_call_inject(h);
if (ret) {
ret = AVERROR_EXIT;
goto fail;
}
if (!c->app_io_ctrl.is_handled)
goto fail;
av_log(h, AV_LOG_INFO, "%s: will reseek(%d) at pos=%"PRId64", whence=%d\n", __func__, c->app_io_ctrl.retry_counter, pos, whence);
seek_ret = ijkhttphook_reseek_at(h, pos, whence, c->app_io_ctrl.is_url_changed);
av_log(h, AV_LOG_INFO, "%s: did reseek(%d) at pos=%"PRId64", whence=%d: %"PRId64"\n", __func__, c->app_io_ctrl.retry_counter, pos, whence, seek_ret);
}
if (c->test_fail_point)
c->test_fail_point_next = c->logical_pos + c->test_fail_point;
c->io_error = 0;
return c->logical_pos;
fail:
return ret;
}
关闭URLContext
static int ijkurlhook_close(URLContext *h)
{
Context *c = h->priv_data;
av_dict_free(&c->inner_options);
//关闭URLContext
return ffurl_closep(&c->inner);
}
先看下http.c中http_open中的操作
static int http_open(URLContext *h, const char *uri, int flags,
AVDictionary **options)
{
HTTPContext *s = h->priv_data;
int ret;
s->app_ctx = (AVApplicationContext *)(intptr_t)s->app_ctx_intptr;
if( s->seekable == 1 )
h->is_streamed = 0;
else
h->is_streamed = 1;
s->filesize = UINT64_MAX;
s->location = av_strdup(uri);
if (!s->location)
return AVERROR(ENOMEM);
if (options)
av_dict_copy(&s->chained_options, *options, 0);
if (s->headers) {
int len = strlen(s->headers);
if (len < 2 || strcmp("\r\n", s->headers + len - 2)) {
av_log(h, AV_LOG_WARNING,
"No trailing CRLF found in HTTP header.\n");
ret = av_reallocp(&s->headers, len + 3);
if (ret < 0)
return ret;
s->headers[len] = '\r';
s->headers[len + 1] = '\n';
s->headers[len + 2] = '\0';
}
}
if (s->listen) {
return http_listen(h, uri, flags, options);
}
//发送http即将连接的指令
av_application_will_http_open(s->app_ctx, (void*)h, uri);
ret = http_open_cnx(h, options);
//发送http已经连接的指令
av_application_did_http_open(s->app_ctx, (void*)h, uri, ret, s->http_code, s->filesize);
if (ret < 0)
av_dict_free(&s->chained_options);
return ret;
}
跟踪av_application_will_http_open函数,可以看到发送了event_type:AVAPP_EVENT_WILL_HTTP_OPEN,这里会拿到java层设置的值,如果失败则不断hook重试。
void av_application_will_http_open(AVApplicationContext *h, void *obj, const char *url)
{
AVAppHttpEvent event = {0};
if (!h || !obj || !url)
return;
event.obj = obj;
av_strlcpy(event.url, url, sizeof(event.url));
av_application_on_http_event(h, AVAPP_EVENT_WILL_HTTP_OPEN, &event);
}
下面看下tcp.c中的一段代码实现来举例,我们可以看到这里直接取ijkplayer_jni.c中的inject_callback函数返回值来判断。
ret = av_application_on_tcp_will_open(s->app_ctx);
if (ret) {
av_log(NULL, AV_LOG_WARNING, "terminated by application in AVAPP_CTRL_WILL_TCP_OPEN");
goto fail1;
}
跟踪av_application_on_tcp_will_open/application.c函数
int av_application_on_tcp_will_open(AVApplicationContext *h)
{
if (h && h->func_on_app_event) {
AVAppTcpIOControl control = {0};
return h->func_on_app_event(h, AVAPP_CTRL_WILL_TCP_OPEN, (void *)&control, sizeof(AVAppTcpIOControl));
}
return 0;
}
func_on_app_event函数指针的初始化在第二章已经分析过了,最后调到jni层的inject_callback。
其实没有很明白为什么要在协议中加这些发送event的处理事件。