ijkplayer-hook协议实现分析

本文主要分析ijk中hook协议的实现流程和具体实现来进行分析。

1.ijkhttphook的使用

先看一下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;
            }
        });

2.hook协议的实现方式

下面分析断网重连等实现方式:

先看一下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协议的实现流程后,我们简单梳理下流程,

3.ijkhookhttp的实现分析

ijkhttphook与ijktcphook的实现都在ijkurlhook.c文件中,下面我们开始分析ijkhttphook协议。

3.1ijkhttphook_open的实现

我们先看下打开协议的实现函数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函数

3.1.1.ijkurlhook_init实现

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

3.1.2.ijkurlhook_call_inject函数

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

3.1.3.ijkurlhook_reconnect函数

在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函数会不断尝试建立连接。

3.2.ijkhttphook_read函数实现

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

3.3ijkhttphook_seek函数实现

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

3.4ijkurlhook_close函数实现

关闭URLContext

static int ijkurlhook_close(URLContext *h)
{
    Context *c = h->priv_data;
 
    av_dict_free(&c->inner_options);
    //关闭URLContext
    return ffurl_closep(&c->inner);
}

4.在协议中与java层通信

先看下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的处理事件。

你可能感兴趣的:(音视频,ffmpeg)