博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
android ijkplayer c层分析-初始化
阅读量:7001 次
发布时间:2019-06-27

本文共 14557 字,大约阅读时间需要 48 分钟。

本来这个过程我是不大想写初始化的过程,觉得网上已经有不少文章来分析了。但是在前面的整个分析过程中,暴露了自己对一些问题理解还不够透彻,因此有必要做一次。

首先是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
(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 voidIjkMediaPlayer_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 intinject_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("
.onNativeInvoke()"); @SuppressWarnings("unchecked") WeakReference
weakPlayer = (WeakReference
) weakThiz; IjkMediaPlayer player = weakPlayer.get(); if (player == null) throw new IllegalStateException("
.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() =
")); 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://umrvl.baihongyu.com/

你可能感兴趣的文章
Win7远程桌面以及远程关机设置注意事项
查看>>
写给想做前端的你
查看>>
First angular 2 app
查看>>
css边框入门
查看>>
从零开始写个编译器吧 - 分析非终结符
查看>>
由ES规范学JavaScript(二):深入理解“连等赋值”问题
查看>>
4个小例子告诉你:如何成为一名数据极客
查看>>
「JavaScript」JS两种服务端相关跨域方法详解
查看>>
[Leetcode] Sqrt 开方
查看>>
fir.im Weekly - 17 个提升 iOS 开发效率的必备工具
查看>>
Restful API 的设计
查看>>
Apache Lucene 8.0.0 发布,Java 全文搜索引擎
查看>>
Sequelize 和 MySQL 对照
查看>>
如何自学编程?学习方法在这里!
查看>>
好程序员web前端分享js剪切板Clipboard.js 使用
查看>>
访问权限,public private protected
查看>>
Kubernetes(一) - Docker管理工具
查看>>
Linux基础命令---文本编辑ex
查看>>
用开放和流动反抗熵增,生态建设终极哲学——保险生态建设 ...
查看>>
web页面渲染(二)
查看>>