IJKPLAYER源码分析-主结构

news2024/11/20 20:39:08

前言

    本文主要分析IJKPLAYER源码软解主流程,硬解将另起一篇分析。所用IJKPLAYER版本号:

#define IJKPLAYER_VERSION "f0.7.17-28-gd7040f97"

主结构

    IJKPLAYER播放器的解协议、解复用、解码、音视频同步与显示播放,以及主要线程等主流程,大致如下:

read_thread职责:

  • 解协议:但凡ffmpeg所支持的协议,均支持,包括http-flv/rtsp/rtmp/rtp/srt/hls等流媒体协议,以及file协议-本地文件等;
  • demux:解协议之后,还需demux,调用av_read_frame分离出audio和video以及subtitle压缩数据,分别入对应的压缩队列;
  • seek:回放的seek请求操作,实际是放在read_thread线程做的:ijkmp_seek_to_l => ffp_seek_to_l => stream_seek => avformat_seek_file请求最近的IDR帧:

ffplay_video_thread职责:

  • video解码:消费videoq(PacketQueue) 队列的video压缩数据,用ffmpeg软解,decode出pixel数据之后,入pictq(FrameQueue)队列,待video_refresh_thread消费显示;
  • accurate seek: 若app层enable了精准seek,则会对decode出的video pts和seek timestamp作比对,若pts < seek_timestamp,则丢弃之,不render;
  • vf:video filter,缺省关闭;

video_refresh_thread职责:

  • 音视频同步:若主时钟不是AV_SYNC_VIDEO_MASTER,则video需与audio做pts时钟同步,详见另外一篇介绍音视频同步的文章;相反,则按video的duration播放;
  • SDL显示video:video_display2(ffp) => video_image_display2(ffp) => SDL_VoutDisplayYUVOverlay(ffp->vout, vp->bmp);
  • 更新video时钟:update_video_pts(is, vp->pts, vp->pos, vp->serial); 

audio_thread职责:

  • audio解码:消费audioq(PacketQueue)队列的audio压缩数据,用ffmpeg软解,decode出pcm数据之后,入sampq(FrameQueue)队列,待声卡驱动copy数据播放;
  • accurate seek: 同video的accurate seek逻辑;
  • af:audio filter,缺省关闭;

sdl_audio_callback职责:

  • 声卡驱动播放:与video主动送pixel数据给显卡驱动不一样,audio是由声卡主动获取pcm数据,具体是由sdl_audio_callback驱动;
  • 重采样:若audio参数(指采样率、采样通道与采样位数)变更,或同步video需丢帧或添帧时,则需要重采样;
  • 更新audio的时钟:set_clock_at => sync_clock_to_slave;

subtitle_thread职责:

后续再讨论字幕

消息循环

通信模型

    那么,IJKPLAYER与APP业务层是如何沟通的呢?查看IJKPLAYER源码得知,除了subtitle_thread线程(当前和业务线程无交流)以外,其他几个主要线程均通过MessageQueue消息队列与上层通信:

    可以看到,MessageQueue是典型的多生产者单消费者模型队列,是一个带有首尾指针和回收链表的单链表结构,其详细定义参照如下:

typedef struct MessageQueue {
    AVMessage *first_msg, *last_msg;
    int nb_messages;
    int abort_request;
    SDL_mutex *mutex;
    SDL_cond *cond;

    AVMessage *recycle_msg;
    int recycle_count;
    int alloc_count;
} MessageQueue;

    单个AVMessage结构如下,是一个带有资源释放的单链表:

typedef struct AVMessage {
    int what;
    int arg1;
    int arg2;
    void *obj;
    size_t len;
    void (*free_l)(void *obj);
    struct AVMessage *next;
} AVMessage;

 AVMessage生产者接口

inline static void ffp_notify_msg1(FFPlayer *ffp, int what) {
    msg_queue_put_simple3(&ffp->msg_queue, what, 0, 0);
}

inline static void ffp_notify_msg2(FFPlayer *ffp, int what, int arg1) {
    msg_queue_put_simple3(&ffp->msg_queue, what, arg1, 0);
}

inline static void ffp_notify_msg3(FFPlayer *ffp, int what, int arg1, int arg2) {
    msg_queue_put_simple3(&ffp->msg_queue, what, arg1, arg2);
}

inline static void ffp_notify_msg4(FFPlayer *ffp, int what, int arg1, int arg2, void *obj, int obj_len) {
    msg_queue_put_simple4(&ffp->msg_queue, what, arg1, arg2, obj, obj_len);
}

inline static void ffp_notify_msg5(FFPlayer *ffp, int what, int arg1, int arg2, void *obj,
        size_t len, void (*free_l)(void *obj)) {
    msg_queue_put_simple5(&ffp->msg_queue, what, arg1, arg2, obj, len, free_l);
}

inline static void ffp_remove_msg(FFPlayer *ffp, int what) {
    msg_queue_remove(&ffp->msg_queue, what);
}

AVMessage消费者接口

int retval = ijkmp_get_msg(mp, &msg, 1);

向业务层投递AVMessage的接口

    IJKPLAYER内核投递msg的接口:

post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);

post_event2(env, weak_thiz, FFP_MSG_GET_IMG_STATE, msg.arg1, msg.arg2, file_name);
inline static void post_event(JNIEnv *env, jobject weak_this, int what, int arg1, int arg2)
{
    // MPTRACE("post_event(%p, %p, %d, %d, %d)", (void*)env, (void*) weak_this, what, arg1, arg2);
    J4AC_IjkMediaPlayer__postEventFromNative(env, weak_this, what, arg1, arg2, NULL);
    // MPTRACE("post_event()=void");
}
void J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer__postEventFromNative(JNIEnv *env, jobject weakThiz, jint what, jint arg1, jint arg2, jobject obj)
{
    (*env)->CallStaticVoidMethod(env, class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.id, class_J4AC_tv_danmaku_ijk_media_player_IjkMediaPlayer.method_postEventFromNative, weakThiz, what, arg1, arg2, obj);
}

    android端最后是通过反射,由native层调用到Java层postEventFromNative方法,将msg传递到业务层的: 

    @CalledByNative
    private static void postEventFromNative(Object weakThiz, int what,
            int arg1, int arg2, Object obj) {
        if (weakThiz == null)
            return;

        @SuppressWarnings("rawtypes")
        IjkMediaPlayer mp = (IjkMediaPlayer) ((WeakReference) weakThiz).get();
        if (mp == null) {
            return;
        }

        if (what == IJK_MSG_VIDEO_SNAP_SHOT) {
            ByteBuffer byteBuffer = (ByteBuffer) obj;
            Matrix matrix = new Matrix();
            matrix.postScale(1, -1, arg1/2f, arg2/2f);
            Bitmap bitmap = Bitmap.createBitmap(arg1, arg2, Bitmap.Config.ARGB_8888);
            bitmap.copyPixelsFromBuffer(byteBuffer);
            bitmap =  Bitmap.createBitmap(bitmap, 0, 0, arg1, arg2, matrix, true);
            obj = bitmap;
        }
        // native ijkplayer never post this message
        // if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
        //     // this acquires the wakelock if needed, and sets the client side
        //     // state
        //     mp.start();
        // }
        if (mp.mEventHandler != null) {
            Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
            mp.mEventHandler.sendMessage(m);
        }
    }

消息循环线程

    查看IJKPLAYER源码得知,消息循环线程并不在IJKPLAYER内核,而在移动端native层(ANDROID在jni层、iOS则在oc层)。

ANDROID端消息循环线程

    ANDROID端消息循环线程方法,是通过ijkmp_android_create注册的回调:

IjkMediaPlayer *mp = ijkmp_android_create(message_loop);

    消息循环线程方法:

static int message_loop(void *arg)
{
    MPTRACE("%s\n", __func__);

    JNIEnv *env = NULL;
    if (JNI_OK != SDL_JNI_SetupThreadEnv(&env)) {
        ALOGE("%s: SetupThreadEnv failed\n", __func__);
        return -1;
    }

    IjkMediaPlayer *mp = (IjkMediaPlayer*) arg;
    JNI_CHECK_GOTO(mp, env, NULL, "mpjni: native_message_loop: null mp", LABEL_RETURN);

    message_loop_n(env, mp);

LABEL_RETURN:
    ijkmp_dec_ref_p(&mp);

    MPTRACE("message_loop exit");
    return 0;
}
static void message_loop_n(JNIEnv *env, IjkMediaPlayer *mp)
{
    jobject weak_thiz = (jobject) ijkmp_get_weak_thiz(mp);
    JNI_CHECK_GOTO(weak_thiz, env, NULL, "mpjni: message_loop_n: null weak_thiz", LABEL_RETURN);

    while (1) {
        AVMessage msg;

        int retval = ijkmp_get_msg(mp, &msg, 1);
        if (retval < 0)
            break;

        // block-get should never return 0
        assert(retval > 0);

        switch (msg.what) {
        case FFP_MSG_FLUSH:
            MPTRACE("FFP_MSG_FLUSH:\n");
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_ERROR:
            MPTRACE("FFP_MSG_ERROR: %d\n", msg.arg1);
            if (msg.obj) {
                jstring text = (*env)->NewStringUTF(env, (const char *)msg.obj);
                post_event2(env, weak_thiz, msg.what, msg.arg1, msg.arg2, text);
                J4A_DeleteLocalRef__p(env, &text);
            } else {
                post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            }
            break;
        case FFP_MSG_PREPARED:
            MPTRACE("FFP_MSG_PREPARED:\n");
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_COMPLETED:
            MPTRACE("FFP_MSG_COMPLETED:\n");
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_VIDEO_SIZE_CHANGED:
            MPTRACE("FFP_MSG_VIDEO_SIZE_CHANGED: %d, %d\n", msg.arg1, msg.arg2);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_SAR_CHANGED:
            MPTRACE("FFP_MSG_SAR_CHANGED: %d, %d\n", msg.arg1, msg.arg2);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_VIDEO_RENDERING_START:
            MPTRACE("FFP_MSG_VIDEO_RENDERING_START:\n");
            // post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_VIDEO_RENDERING_START, 0);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_AUDIO_RENDERING_START:
            MPTRACE("FFP_MSG_AUDIO_RENDERING_START:\n");
            // post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_AUDIO_RENDERING_START, 0);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_VIDEO_ROTATION_CHANGED:
            MPTRACE("FFP_MSG_VIDEO_ROTATION_CHANGED: %d\n", msg.arg1);
            // post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_VIDEO_ROTATION_CHANGED, msg.arg1);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_AUDIO_DECODED_START:
            MPTRACE("FFP_MSG_AUDIO_DECODED_START:\n");
            // post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_AUDIO_DECODED_START, 0);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_VIDEO_DECODED_START:
            MPTRACE("FFP_MSG_VIDEO_DECODED_START:\n");
            // post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_VIDEO_DECODED_START, 0);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_OPEN_INPUT:
            MPTRACE("FFP_MSG_OPEN_INPUT:\n");
            //post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_OPEN_INPUT, 0);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_FIND_STREAM_INFO:
            MPTRACE("FFP_MSG_FIND_STREAM_INFO:\n");
            //post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_FIND_STREAM_INFO, 0);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_COMPONENT_OPEN:
            MPTRACE("FFP_MSG_COMPONENT_OPEN:\n");
            //post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_COMPONENT_OPEN, 0);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_BUFFERING_START:
            MPTRACE("FFP_MSG_BUFFERING_START:\n");
            // post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_BUFFERING_START, msg.arg1);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_BUFFERING_END:
            MPTRACE("FFP_MSG_BUFFERING_END:\n");
            // post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_BUFFERING_END, msg.arg1);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_BUFFERING_UPDATE:
        case FFP_MSG_CURRENT_POSITION_UPDATE:
        case FFP_MSG_DISKBUFFER_UPDATE:
            // MPTRACE("FFP_MSG_BUFFERING_UPDATE: %d, %d", msg.arg1, msg.arg2);
            // post_event(env, weak_thiz, MEDIA_BUFFERING_UPDATE, msg.arg1, msg.arg2);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_BUFFERING_BYTES_UPDATE:
            break;
        case FFP_MSG_BUFFERING_TIME_UPDATE:
            break;
        case FFP_MSG_SEEK_COMPLETE:
            MPTRACE("FFP_MSG_SEEK_COMPLETE:\n");
            // post_event(env, weak_thiz, MEDIA_SEEK_COMPLETE, 0, 0);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_ACCURATE_SEEK_COMPLETE:
            MPTRACE("FFP_MSG_ACCURATE_SEEK_COMPLETE:\n");
            // post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_MEDIA_ACCURATE_SEEK_COMPLETE, msg.arg1);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_PLAYBACK_STATE_CHANGED:
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_TIMED_TEXT:
            if (msg.obj) {
                jstring text = (*env)->NewStringUTF(env, (const char *)msg.obj);
                post_event2(env, weak_thiz, FFP_MSG_TIMED_TEXT, 0, 0, text);
                J4A_DeleteLocalRef__p(env, &text);
            }
            else {
                post_event2(env, weak_thiz, FFP_MSG_TIMED_TEXT, 0, 0, NULL);
            }
            break;
        case FFP_MSG_GET_IMG_STATE:
            if (msg.obj) {
                jstring file_name = (*env)->NewStringUTF(env, (char *)msg.obj);
                post_event2(env, weak_thiz, FFP_MSG_GET_IMG_STATE, msg.arg1, msg.arg2, file_name);
                J4A_DeleteLocalRef__p(env, &file_name);
            }
            else {
                post_event2(env, weak_thiz, FFP_MSG_GET_IMG_STATE, msg.arg1, msg.arg2, NULL);
            }
            break;
        case FFP_MSG_VIDEO_SNAP_SHOT:
            if (msg.obj) {
                jobject byte_buffer = (*env)->NewDirectByteBuffer(env, msg.obj, msg.len);
                post_event2(env, weak_thiz, FFP_MSG_VIDEO_SNAP_SHOT, msg.arg1, msg.arg2, byte_buffer);
            }
            break;
        case FFP_MSG_DOWNLOAD_HLS_PROGRESS:
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_VIDEO_SEEK_RENDERING_START:
            MPTRACE("FFP_MSG_VIDEO_SEEK_RENDERING_START:\n");
            // post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_VIDEO_SEEK_RENDERING_START, msg.arg1);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        case FFP_MSG_AUDIO_SEEK_RENDERING_START:
            MPTRACE("FFP_MSG_AUDIO_SEEK_RENDERING_START:\n");
            // post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_AUDIO_SEEK_RENDERING_START, msg.arg1);
            post_event(env, weak_thiz, msg.what, msg.arg1, msg.arg2);
            break;
        default:
            ALOGE("unknown FFP_MSG_xxx(%d)\n", msg.what);
            break;
        }
        msg_free_res(&msg);
    }

LABEL_RETURN:
    ;
}

 iOS端消息循环线程

    iOS端的msg loop方法,是则ijkmp_ios_create方法里注册的callback:

_nativeMediaPlayer = ijkmp_ios_create(ff_media_player_msg_loop);

    其消息循环线程实现: 

int ff_media_player_msg_loop(void* arg)
{
    @autoreleasepool {
        IjkMediaPlayer *mp = (IjkMediaPlayer*)arg;
        __weak IJKFFMediaPlayer *ffPlayer = ffplayerRetain(ijkmp_set_weak_thiz(mp, NULL));
        while (ffPlayer) {
            @autoreleasepool {
                IJKFFMoviePlayerMessage *msg = [ffPlayer obtainMessage];
                if (!msg)
                    break;
                
                int retval = ijkmp_get_msg(mp, &msg->_msg, 1);
                if (retval < 0)
                    break;
                
                // block-get should never return 0
                assert(retval > 0);
                [ffPlayer performSelectorOnMainThread:@selector(postEvent:) withObject:msg waitUntilDone:NO];
            }
        }
        
        // retained in prepare_async, before SDL_CreateThreadEx
        ijkmp_dec_ref_p(&mp);
        return 0;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/454503.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能家居项目整合(网络控制线程、语音控制线程,火灾报警线程)

mainPro.c&#xff08;主函数&#xff09; #include <stdio.h> #include <string.h> #include "contrlEquipments.h" #include "inputCommand.h" #include <pthread.h> #include <unistd.h>struct Equipment *findEquipByName(ch…

人工智能在医疗保健中的应用与创新

引言 随着科技的不断发展&#xff0c;人工智能&#xff08;AI&#xff09;逐渐在各个领域展现出巨大的潜力&#xff0c;特别是在医疗保健行业。人工智能技术的引入为医疗保健领域带来了创新的诊断和治疗方法&#xff0c;提高了病患的生活质量和医疗保健效率。本文将探讨人工智…

倾斜摄影三维模型轻量化过程中遇到的常见问题分析,如何处理这些问题?

倾斜摄影三维模型轻量化过程中遇到的常见问题分析&#xff0c;如何处理这些问题&#xff1f; 在倾斜摄影超大场景的三维模型轻量化过程中&#xff0c;常见的问题包括&#xff1a; 1、精度损失。为了减小数据文件大小&#xff0c;轻量化处理可能会破坏原始数据的精度&#xff0…

数字未来:世界正走向新的“破茧时刻”

著名科学史专家亚历山大柯瓦雷&#xff0c;在《从封闭世界到无限宇宙》展示了一段非常神奇的历史现象&#xff1a;人类从笃信自己生活在一个封闭空间&#xff0c;到认识浩瀚无垠的宇宙&#xff0c;其实并没有耗费很长时间。自1543年哥白尼发布《天体运行论》&#xff0c;到牛顿…

基于深度学习和生理信号的疾病筛查:个体内和个体间研究的价值与应用

一、引言 随着深度学习技术的飞速发展&#xff0c;基于生理信号的疾病筛查和诊断方法在医学领域得到了广泛应用。这些方法通常利用个体内和个体间的生理信号数据&#xff0c;通过训练深度学习模型实现疾病的自动识别和预测。本文将讨论个体内和个体间研究在这一领域的价值和应…

Windows下virtualbox相关软件安装设置全过程

一、下载 virtual box 程序 virtual box扩展程序-Oracle_VM_VirtualBox_Extension_Pack-7.0.8.vbox-extpack Virtualbox GuestAdditions 程序-解决分辨率&#xff0c;主机虚拟机之间共享文件、剪贴板等问题 http://download.virtualbox.org/virtualbox/7.0.8/ 或者 virtual b…

倾斜摄影三维模型转换3DTILTES格式遇到的常见问题

倾斜摄影三维模型转换3DTILTES格式遇到的常见问题 将倾斜摄影三维模型从OSGB格式转换为3DTILES格式时&#xff0c;常见的问题包括&#xff1a; 1、3D Tiles生成时间较长&#xff1a;由于3D Tiles是一种高效的地理数据存储格式&#xff0c;能够支持海量的空间数据呈现和查询&am…

【LeetCode: 5. 最长回文子串 | 暴力递归=>记忆化搜索=>动态规划 => 中心扩展法】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

可以一学的代码优化小技巧:减少if-else冗余

前言 if-else 语句对于程序员来说&#xff0c;是非常非常熟悉的一个判断语句&#xff0c;我们在日常开发和学习中都经常看见它&#xff0c;if-else语句主要用于需要做出选择的地方进行判断&#xff0c;这里就不再赘述if-else语法和特点了。 ​ 我们在写代码&#xff08;如图下…

vue---组件逻辑复用方法:Mixin/HOC/Renderless组件

目录 1、Mixin 2、HOC 3、Renderless组件 下文通过表单校验来分别讲解Mixin/HOC/Renderless组件这三种方式。 1、Mixin 通过mixin将一个公用的validate函数同步到每一个组件中去 mixin使用详细介绍见&#xff1a;vue---mixin混入_maidu_xbd的博客-CSDN博客一个混入对象可…

优化Dynamics 365建议

传统上&#xff0c;旧版 Web 客户端需要某些扩展&#xff08;如功能区规则&#xff09;同步返回&#xff0c;这意味着开发人员在从远程源请求数据时被迫使用同步请求。在统一接口中&#xff0c;我们已采取措施确保支持异步通信。例如&#xff1a; 统一接口支持异步功能区规则评…

p67 内网安全-域横向 smbwmi 明文或 hash 传递

数据来源 知识点1&#xff1a; Windows2012以上版本默认关闭wdigest&#xff0c;攻击者无法从内存中获取明文密码 Windows2012以下版本如安装KB2871997补丁&#xff0c;同样也会导致无法获取明文密码 针对以上情况&#xff0c;我们提供了4种方式解决此类问题 利用哈希hash传递&…

Spring Security详细使用

认证流程 1.集中式认证流程 &#xff08;1&#xff09;用户认证 使用UsernamePasswordAuthenticationFilter过滤器中attemptAuthentication方法实现认证功能&#xff0c;该过滤器父类中successfulAuthentication方法实现认证成功后的操作 &#xff08;2&#xff09;身份校验…

基于opencv-python的二值图像处理

目录 阈值 腐蚀与膨胀 开运算与闭运算 连通区域分析 轮廓 一、阈值 按照颜色对图像进行分类&#xff0c;可以分为彩色图像、灰度图像和二值图像。灰度图像是只含亮度信息&#xff0c;不含色彩信息的图像。灰度化处理是把彩色图像转换为灰度图像的过程&#xff0c;是图像处…

【Linux】popen pclose接口介绍

本篇文章简单讲述了c语言接口popen/pclose的用法 1.函数作用 函数定义如下 #include <stdio.h>FILE *popen(const char *command, const char *type); int pclose(FILE *stream);1.1 popen popen函数会创建一个管道&#xff0c;fork后调用shell来打开进程。由于管道的…

Junit 5 如何使用 Guice DI

Guice 是一个依赖注入的小清新工具。 相比 Spring 的依赖管理来说&#xff0c;这个工具更加小巧&#xff0c;我们可以在测试中直接使用。 Junit 5 在 Junit 中使用就没有那么方便了&#xff0c;因为 Junit 没有 Guice 的注解。 你需要手动写一个类&#xff0c;在这个类中&a…

SpringCloud入门实战(七)-Hystrix服务熔断

&#x1f4dd; 学技术、更要掌握学习的方法&#xff0c;一起学习&#xff0c;让进步发生 &#x1f469;&#x1f3fb; 作者&#xff1a;一只IT攻城狮 。 &#x1f490;学习建议&#xff1a;1、养成习惯&#xff0c;学习java的任何一个技术&#xff0c;都可以先去官网先看看&…

Spring的作用域与生命周期

文章目录 一、lombok的安装与使用二、Spring作用域二、Bean原理分析执行流程Bean的生命周期 一、lombok的安装与使用 lombok插件可以提供给我们一些注释&#xff0c;这些注释可以很好的帮助我们消除Java代码中大量冗余的代码&#xff0c;可以使得我们的Java类可以看起来非常的…

OpenCV实战——二值特征描述符

OpenCV实战——二值特征描述符 0. 前言1. ORB 和 BRISK 二值描述符1. ORB 特征描述符1.2 ORB 与 BRISK 算法 2. FREAK 二值描述符3. 二值描述符采样模式4. 完整代码相关链接 0. 前言 在《特征描述符》一节中&#xff0c;我们学习了如何使用从图像强度梯度中提取的描述符来描述…

ChatGPT使用学习(一):chatgpt_academic安装到测试详细教程(一文包会)

ChatGPT 1.简介及功能2.前置准备3.开始使用 1.简介及功能 Chargpt academic是一种基于OpenAI GPT模型的语言生成模型&#xff0c;它是专门为学术研究者和学生设计的。它使用预训练模型来生成与学术论文、文章和文献相关的文本&#xff0c;可以用于自然语言处理、机器翻译、文本…