音视频开发十九:SDL线程和事件

news2024/9/28 14:16:43

SDL事件相关API

DL_Event 是SDL库中使用的一个数据结构,用于表示系统中发生的事件。一个事件可以是键盘按键、鼠标移动、游戏手柄移动、窗口事件等。SDL_Event 结构包含有关事件类型、事件发生时间和事件参数的详细信息。

SDL_Event 结构具有以下成员:

  • type:表示事件类型的整数。
    • SDL_QUIT:表示退出事件,当用户关闭窗口或按下键盘上的 Alt + F4 或 Cmd + Q 组合键时会触发该事件。
    • SDL_KEYDOWN / SDL_KEYUP:表示键盘按键事件,当用户按下或释放键盘上的按键时会触发该事件。
    • SDL_MOUSEMOTION / SDL_MOUSEBUTTONDOWN / SDL_MOUSEBUTTONUP:表示鼠标事件,当用户移动鼠标或按下、释放鼠标按钮时会触发该事件。
    • SDL_WINDOWEVENT:表示窗口事件,当窗口状态发生变化时会触发该事件,例如窗口大小发生变化,窗口获得或失去焦点等。
    • SDL_JOYAXISMOTION / SDL_JOYBALLMOTION / SDL_JOYHATMOTION / SDL_JOYBUTTONDOWN / SDL_JOYBUTTONUP / SDL_JOYDEVICEADDED / SDL_JOYDEVICEREMOVED:表示游戏手柄事件,当游戏手柄状态发生变化时会触发该事件,例如手柄移动、按下、释放按钮等。
    • SDL_CONTROLLERAXISMOTION / SDL_CONTROLLERBUTTONDOWN / SDL_CONTROLLERBUTTONUP / SDL_CONTROLLERDEVICEADDED / SDL_CONTROLLERDEVICEREMOVED:表示控制器事件,当控制器状态发生变化时会触发该事件,例如控制器移动、按下、释放按钮等。
    • SDL_USEREVENT:表示用户自定义事件,当应用程序调用 SDL_PushEvent 函数将自定义事件添加到事件队列时,会触发该事件。
  • timestamp:表示事件发生时间的 Uint32 类型数值。
  • window:表示事件所属的 SDL_Window 数据指针。
  • key:表示键盘事件的 SDL_KeyboardEvent 数据结构。
  • motion:表示鼠标移动事件的 SDL_MouseMotionEvent 数据结构。
  • button:表示鼠标按钮事件的 SDL_MouseButtonEvent 数据结构。
  • wheel:表示鼠标滚轮事件的 SDL_MouseWheelEvent 数据结构。
  • jaxis:表示游戏手柄轴移动事件的 SDL_JoyAxisEvent 数据结构。
  • jball:表示游戏手柄球移动事件的 SDL_JoyBallEvent 数据结构。
  • jbutton:表示游戏手柄按钮事件的 SDL_JoyButtonEvent 数据结构。
  • jdevice:表示游戏手柄设备事件的 SDL_JoyDeviceEvent 数据结构。
  • caxis:表示游戏手柄轴移动事件的 SDL_ControllerAxisEvent 数据结构。
  • cbutton:表示游戏手柄按钮事件的 SDL_ControllerButtonEvent 数据结构。
  • cdevice:表示游戏手柄设备事件的 SDL_ControllerDeviceEvent 数据结构。
  • quit:表示退出事件的 SDL_QuitEvent 数据结构。
  • user:表示用户自定义事件的 SDL_UserEvent 数据结构。

通过处理 SDL_Event 结构数据,应用程序可以处理用户输入和系统事件,以产生交互行为。

SDL_PollEvent()

SDL_PollEvent()是SDL事件处理库中的一个函数,用于从事件队列中轮询(一直循环)事件并返回。它可以用于捕获用户输入,控制游戏状态等。

在调用SDL_PollEvent()函数时,它会检查SDL事件队列中是否有事件,并将此事件从队列顶部弹出并返回。如果事件队列为空,则该函数将立即返回0。如果有事件,则该函数将返回1,并将事件结构体填充到指定的SDL_Event结构体中。根据事件类型的不同,SDL_Event结构体的不同字段将被填充。

以下是一个简单的SDL_PollEvent()函数的示例,它只处理鼠标事件:

SDL_Event event;

while (SDL_PollEvent(&event)) {
    switch (event.type) {
        case SDL_MOUSEMOTION:
            // 处理鼠标移动事件
            break;
        case SDL_MOUSEBUTTONDOWN:
            // 处理鼠标按下事件
            break;
        case SDL_MOUSEBUTTONUP:
            // 处理鼠标释放事件
            break;
        default:
            break;
    }
}

由于SDL_PollEvent()函数是轮询事件队列的,因此它不会占用大量资源,可在循环中反复使用。但要注意,如果在事件处理期间执行某些任务会导致轮询事件的延迟。

SDL_WaitEvent

SDL_WaitEvent()是SDL事件处理库中的一个函数,用于等待并获取下一个事件。它可用于阻塞程序执行,直到有事件发生。

在调用SDL_WaitEvent()函数时,它会检查事件队列中是否有事件。如果事件队列为空,则该函数会阻塞当前线程并等待事件的到来。如果队列中有事件,则该函数将该事件弹出队列并返回一个非零值。与SDL_PollEvent()不同,不需要指定要轮询的事件类型。

以下是一个简单的SDL_WaitEvent()函数的示例,它等待并处理一个QUIT事件。QUIT事件表示用户关闭窗口或程序正在退出:

SDL_Event event;

while (SDL_WaitEvent(&event)) {
    if (event.type == SDL_QUIT) {
        // 处理退出事件
        break;
    }
}

需要注意的是,SDL_WaitEvent()函数会阻塞当前线程,因此如果等待事件的时间过长,将会导致程序停滞,并不利于程序的执行。建议在需要响应用户输入的情况下使用SDL_WaitEvent(),在不需要响应事件的情况下建议使用SDL_PollEvent()。

SDL 线程相关API

示例代码:

	#include <SDL.h>
	#include <stdio.h>
	
	SDL_mutex *mutex = NULL;
	SDL_cond *cond = NULL;
	
	int thread_work(void *arg) {
	
	  printf("Thread-2 Lock Mutex Start\n");
	  SDL_LockMutex(mutex);
	  printf("Thread-2 Lock Mutex Success\n");
	
	  printf("Thread-2 Working with Mutex\n");
	  sleep(4);
	
	  printf("Thread-2 Wait Condition Start\n");
	  SDL_CondWait(cond, mutex);
	  printf("Thread-2 Wait Condition Success\n");
	
	  printf("Thread-2 Unlock Mutex\n");
	  SDL_UnlockMutex(mutex);
	
	  printf("Thread-2 Finish\n");
	
	  return 0;
	}
	
	#undef main
	int main() {
	
	  mutex = SDL_CreateMutex();
	  cond = SDL_CreateCond();
	
	  SDL_Thread *t = SDL_CreateThread(thread_work, "Thread-2", NULL);
	
	  printf("Thread-1 Working\n");
	  sleep(2);
	
	  printf("Thread-1 Lock Mutex Start\n");
	  SDL_LockMutex(mutex);
	  printf("Thread-1 Lock Mutex Success\n");
	
	  printf("Thread-1 Working with Mutex\n");
	  sleep(2);
	
	  printf("Thread-1 Send Condition Signal\n");
	  SDL_CondSignal(cond);
	
	  printf("Thread-1 Working with Mutex\n");
	  sleep(2);
	
	  printf("Thread-1 Unlock Mutex\n");
	  SDL_UnlockMutex(mutex);
	
	  printf("Thread-1 Wait Thread-2 Finish Start\n");
	  SDL_WaitThread(t, NULL);
	  printf("Thread-1 Wait Thread-2 Finish Success\n");
	
	  printf("Thread-1 Destroy Mutex Start\n");
	  printf("Thread-1 Destroy Condition Start\n");
	  SDL_DestroyMutex(mutex);
	  SDL_DestroyCond(cond);
	  printf("Thread-1 Destroy Mutex Success\n");
	  printf("Thread-1 Destroy Condition Success\n");
	
	  return 0;
	}


SDL_CreateThread

SDL_CreateThread函数用于创建一个新线程。其原型如下:

SDL_Thread* SDL_CreateThread(SDL_ThreadFunction fn, const char* name, void* data);

其中,fn是一个线程执行的函数,函数指针的定义是:

typedef int (*SDL_ThreadFunction)(void* data);

它接收一个void指针,指向当前线程的参数数据;name是线程名称;data是传递给线程函数的数据参数。

调用SDL_CreateThread函数后,将会创建一个新的线程,并在其中执行参数fn所指定的线程执行函数。创建完成后,函数会返回一个指向SDL_Thread结构体的指针,这个结构体包含了线程的ID和名称等信息。

示例代码:

// 线程执行函数,接收一个void指针作为参数
int threadFunc(void *data)
{
    // 要执行的线程任务
    return 0;
}

// 在主线程中创建一个新线程
SDL_Thread* thread = SDL_CreateThread(threadFunc, "MyThread", NULL);

在这个示例中,我们通过SDL_CreateThread函数在主线程中创建了一个新线程,用于执行线程执行函数threadFunc。在本例中,函数没有传递任何参数,所以最后的NULL表示不传递数据参数。

SDL_WaitThread

SDL_WaitThread是SDL库提供的一个线程等待函数,它的作用是等待指定的线程执行完毕。函数原型如下:

int SDL_WaitThread(SDL_Thread* thread, int* status);

该函数的第一个参数是SDL_Thread类型的指针,表示要等待的线程。第二个参数是一个整型指针,用于存储线程的退出状态。函数的返回值为0表示线程已正常退出,其他值表示错误。

SDL_WaitThread函数会挂起当前线程,等待指定的线程执行完毕,并接收指定线程的返回状态。如果线程已经退出,函数会立刻返回,并将线程的退出状态存入status指针所指的内存中。

SDL_CreateMutex

SDL_CreateMutex() 是SDL库中用于创建互斥锁(mutex)的函数。互斥锁是一种用于保护共享资源的同步原语,它可以确保同一时间内只有一个线程访问共享资源,以避免数据竞争和并发问题。

SDL_CreateMutex() 的函数原型如下:

SDL_mutex* SDL_CreateMutex(void)

该函数不需要任何参数,它返回一个指向新创建互斥锁的指针(SDL_mutex*类型),如果创建失败则返回 NULL。

SDL_LockMutex

SDL_LockMutex() 是SDL库中用于锁定互斥锁(mutex)的函数

SDL_LockMutex() 的函数原型如下:

int SDL_LockMutex(SDL_mutex* mutex)

该函数的参数为互斥锁指针(SDL_mutex*类型),函数返回一个整型值,表示函数调用的结果。若函数成功锁定互斥锁,则返回0;否则返回错误代码。

当一个线程执行 SDL_LockMutex() 函数时,如果互斥锁当前没有被其他线程锁定,则该线程会立即获得互斥锁并可以访问受保护的共享资源。如果互斥锁当前已经被其他线程锁定,那么该线程会一直阻塞等待直到互斥锁被释放并可以被该线程获得为止。

需要注意的是,在使用互斥锁时,要遵循“谁申请谁释放”的原则,即使用 SDL_LockMutex() 函数锁定互斥锁的线程必须在访问完共享资源后使用 SDL_UnlockMutex() 函数释放互斥锁,否则其他线程将无法获得该锁访问共享资源。

下面是一个简单的示例代码,展示了如何使用 SDL_LockMutex() 函数锁定互斥锁,以避免多线程下的共享资源的并发问题:

#include <SDL.h>

// 定义一个全局变量作为共享的资源
int shared_data = 0;

SDL_mutex* mutex;

void some_function(void) {
    // 等待并锁定互斥锁
    SDL_LockMutex(mutex);

    // 访问受保护的共享资源
    shared_data += 1;

    // 解锁互斥锁
    SDL_UnlockMutex(mutex);
}

int main() {
    // 初始化SDL库
    SDL_Init(SDL_INIT_EVERYTHING);

    // 创建互斥锁
    mutex = SDL_CreateMutex();

    // 启动多个线程访问共享资源
    // ...

    // 最后释放互斥锁
    SDL_DestroyMutex(mutex);

    // 退出SDL库
    SDL_Quit();
    return 0;
}

SDL_UnlockMutex

SDL_LockMutex() 是SDL库中用于释放互斥锁(mutex)的函数

SDL_UnlockMutex 函数的函数原型为:

int SDL_UnlockMutex(SDL_mutex* mutex);

SDL_DestroyMutex

SDL_LockMutex() 是SDL库中用于销毁互斥锁(mutex)的函数

SDL_DestroyMutex 函数的函数原型为:

void SDL_DestroyMutex(SDL_mutex* mutex);

SDL_CreateCond

SDL_CreateCond() 是SDL库中用于创建条件变量(condition variable)的函数。条件变量是一种线程间同步原语,它允许一个线程等待另一个线程满足某个条件后再继续执行,从而更好地控制线程的执行顺序和并发性。

SDL_CreateCond() 的函数原型如下:

SDL_cond* SDL_CreateCond(void)

该函数没有参数,返回一个指向新创建条件变量的指针(SDL_cond*类型),如果创建失败则返回 NULL。

SDL_CondSignal

SDL_CondSignal() 是SDL库中用于发送条件变量(condition variable)信号的函数。

SDL_CondSignal() 的函数原型如下:

int SDL_CondSignal(SDL_cond* cond)

该函数的参数为条件变量指针(SDL_cond*类型),函数返回一个整型值,表示函数调用的结果。若函数成功发送条件变量信号,则返回0;否则返回错误代码。

SDL_CondWait

SDL_CondWait() 是SDL库中用于等待条件变量(condition variable)信号的函数

DL_CondWait() 函数允许线程在等待条件变量时解锁互斥锁,从而让其他线程可以继续访问共享资源

SDL_CondWait() 的函数原型如下:

int SDL_CondWait(SDL_cond* cond, SDL_mutex* mutex)

该函数的参数为条件变量指针(SDL_cond类型)和互斥锁指针(SDL_mutex类型),函数返回一个整型值,表示函数调用的结果。如果条件变量成功地接收到信号,则返回0;否则返回错误代码。

当一个线程调用 SDL_CondWait() 函数时,它会先解锁互斥锁(把自己的锁解开,所以使用SDL_CondWait函数之前,必须先获取到锁),然后一直等待条件变量的信号(SDL_CondSignal或SDL_CondBroadcast),直到信号被发送给该条件变量。当线程接收到信号后,它会重新获取互斥锁(重新获取就是尝试获取,如果其他线程不释放该锁,即使收到信号也不会向下执行。),以访问共享资源。

SDL_DestroyCond

SDL_DestroyCond() 是SDL库中用于销毁条件变量(condition variable)的函数。

SDL_DestroyCond() 的函数原型如下:

void SDL_DestroyCond(SDL_cond* cond)

该函数的参数为条件变量指针(SDL_cond*类型),没有返回值。在销毁条件变量时,需要先保证该条件变量没有被任何线程在等待,然后将其释放掉。

线程应用

通过线程完成一个YUV播放器,每隔40ms更新窗口显示画面

逻辑流程图

代码

#include <stdio.h>
#include <string.h>

#include <SDL.h>

#define BLOCK_SIZE 4096000

//event message
#define REFRESH_EVENT  (SDL_USEREVENT + 1)
#define QUIT_EVENT  (SDL_USEREVENT + 2)

int thread_exit = 0;

// 用线程控制每一帧的播放时间,每隔多少时间触发一个事件通知
int refresh_video_timer(void* udata) {

    thread_exit = 0;

    while (!thread_exit) {
        SDL_Event event;
        event.type = REFRESH_EVENT; // 更新事件
        SDL_PushEvent(&event);
        SDL_Delay(40);
    }

    thread_exit = 0;

    //push quit event
    SDL_Event event;
    event.type = QUIT_EVENT;
    SDL_PushEvent(&event);

    return 0;
}

int yuv_play()
{

    FILE* video_fd = NULL;

    SDL_Event event;
    SDL_Rect rect;

    Uint32 pixformat = 0;

    SDL_Window* win = NULL;
    SDL_Renderer* renderer = NULL;
    SDL_Texture* texture = NULL;

    SDL_Thread* timer_thread = NULL;

    int w_width = 640, w_height = 480;
    const int video_width = 608, video_height = 368;

    Uint8* video_pos = NULL;
    Uint8* video_end = NULL;

    unsigned int remain_len = 0;
    unsigned int video_buff_len = 0;
    unsigned int blank_space_len = 0;
    Uint8 video_buf[BLOCK_SIZE];

    const char* path = "f:\\test_data\\out.yuv";

    // 一个yuv图片的长度 
    /*
        这个公式用于计算YUV格式视频每帧数据的长度(字节数)。
        YUV是一种基于颜色空间的视频格式,其中每个像素点不是一个RGB变量,而是由一个Luma(亮度)和两个色度(Chroma)组成的,通常用YUV 4:2:0的格式存储。在YUV 4:2:0格式中,Y分量的采样率是1,而U和V分量的采样率是1/2,即每四个Y像素只有一个U和V像素。因此,每个像素点需要1.5个字节的存储空间。

        根据这个公式,假设视频的宽度是video_width,高度是video_height,每个像素点需要1.5个字节的存储空间,那么一个YUV帧的大小可以表示为:

        yuv_frame_len = video_width * video_height * 1.5

        这个公式中12/8用于将1.5个字节转化为12位,然后再将结果转化为字节。
    *
    */
    const unsigned int yuv_frame_len = video_width * video_height * 12 / 8;

    //1.初始化SDL
    if (SDL_Init(SDL_INIT_VIDEO)) {
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    //creat window from SDL
    win = SDL_CreateWindow("YUV Player",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        w_width, w_height,
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (!win) {
        fprintf(stderr, "Failed to create window, %s\n", SDL_GetError());
        goto __FAIL;
    }

    // 创建渲染器
    renderer = SDL_CreateRenderer(win, -1, 0);

    //IYUV: Y + U + V  (3 planes)
    //YV12: Y + V + U  (3 planes)
    pixformat = SDL_PIXELFORMAT_IYUV;

    
    //根据渲染器创建纹理
    texture = SDL_CreateTexture(renderer,
        pixformat,
        SDL_TEXTUREACCESS_STREAMING,
        video_width,
        video_height);

    //打开yuv文件
    video_fd = fopen(path, "r");
    if (!video_fd) {
        fprintf(stderr, "Failed to open yuv file\n");
        goto __FAIL;
    }

    //读取yuv数据保存到video_buf中
    if ((video_buff_len = fread(video_buf, 1, BLOCK_SIZE, video_fd)) <= 0) {
        fprintf(stderr, "Failed to read data from yuv file!\n");
        goto __FAIL;
    }

    //set video positon
    video_pos = video_buf;
    video_end = video_buf + video_buff_len;
    blank_space_len = BLOCK_SIZE - video_buff_len;

    timer_thread = SDL_CreateThread(refresh_video_timer,
        NULL,
        NULL);

    do {
        //等待事件
        SDL_WaitEvent(&event);
        if (event.type == REFRESH_EVENT) {
            //not enought data to render
            if ((video_pos + yuv_frame_len) > video_end) {

                //have remain data, but there isn't space
                remain_len = video_end - video_pos;
                if (remain_len && !blank_space_len) {
                    //copy data to header of buffer
                    memcpy(video_buf, video_pos, remain_len);

                    blank_space_len = BLOCK_SIZE - remain_len;
                    video_pos = video_buf;
                    video_end = video_buf + remain_len;
                }

                //at the end of buffer, so rotate to header of buffer
                if (video_end == (video_buf + BLOCK_SIZE)) {
                    video_pos = video_buf;
                    video_end = video_buf;
                    blank_space_len = BLOCK_SIZE;
                }

                //从文件中读取yuv数据缓存到video_end
                if ((video_buff_len = fread(video_end, 1, blank_space_len, video_fd)) <= 0) {
                    fprintf(stderr, "eof, exit thread!");
                    thread_exit = 1;
                    continue;// to wait event for exiting
                }

                //reset video_end
                video_end += video_buff_len;
                blank_space_len -= video_buff_len;
                printf("not enought data: pos:%p, video_end:%p, blank_space_len:%d\n", video_pos, video_end, blank_space_len);
            }

            // 更新纹理
            SDL_UpdateTexture(texture, NULL, video_pos, video_width);

            //FIX: If window is resize
            rect.x = 0;
            rect.y = 0;
            rect.w = w_width;
            rect.h = w_height;

            // 显示到窗口
            SDL_RenderClear(renderer);
            SDL_RenderCopy(renderer, texture, NULL, &rect);
            SDL_RenderPresent(renderer);

            printf("not enought data: pos:%p, video_end:%p, blank_space_len:%d\n", video_pos, video_end, blank_space_len);
            video_pos += yuv_frame_len;

        }
        else if (event.type == SDL_WINDOWEVENT) {
            //If Resize
            SDL_GetWindowSize(win, &w_width, &w_height);
        }
        else if (event.type == SDL_QUIT) {
            thread_exit = 1;
        }
        else if (event.type == QUIT_EVENT) {
            break;
        }
    } while (1);

__FAIL:

    //close file
    if (video_fd) {
        fclose(video_fd);
    }

    SDL_Quit();

    return 0;
}


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

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

相关文章

[5 种有效方法] 适用于 Android 的通用解锁图案/密码

在当今世界&#xff0c;保护您的密码对于您的文件和数据的安全至关重要&#xff0c;尤其是在第三方应用程序盛行的情况下。为这些应用程序注册帐户不是问题&#xff0c;就像记住它们一样。但是&#xff0c;如果您不知何故忘记了手机密码&#xff0c;您仍然可以在不丢失宝贵数据…

突破规模化运维瓶颈 SREWorks云原生数智运维平台揭秘

作者&#xff1a; 钟炯恩——阿里云大数据基础工程技术团队运维专家 引言 突破规模化运维瓶颈是诸多IT规模增长的企业及组织当前遇到的比较棘手的问题。面对这些问题&#xff0c;多数人的第一反应是上云。但是上云之后我们会发现&#xff0c;即使云上的架构规模增大&#xf…

如何使用DNS实现融合CDN功能

将托管DNS解决方案与CDN配对可为您的网站提供额外的性能、可靠性和灵活性。 域名系统&#xff08;DNS&#xff09;是一种用于计算机、服务或连接到Internet或专用网络的任何资源的分层分布式命名系统&#xff0c;它将各种信息与分配给每个参与实体的域名相关联&#xff0c;它基…

ChatGPT 火了,一大批开源平替也来了

ChatGPT 凭一己之力让全球为 AI 沸腾&#xff0c;引发了科技巨头之间的聊天机器人军备竞赛。 众所周知&#xff0c;ChatGPT 的背后技术是 GPT(Generative Pre-trained Transformer)模型。GPT 是一种基于 Transformer 模型的自然语言处理模型&#xff0c;由 OpenAI 公司开发。它…

【elasticsearch部署】

安装elasticsearch 1.部署单点es1.1.创建网络1.2.加载镜像1.3.运行 2.部署kibana2.1.部署2.2.DevTools 3.安装IK分词器3.1.在线安装ik插件&#xff08;较慢&#xff09;3.2.离线安装ik插件&#xff08;推荐&#xff09;1&#xff09;查看数据卷目录2&#xff09;解压缩分词器安…

前端必备的nginx知识点

nginx学习&#xff0c;看这一篇就够了&#xff1a;下载、安装。使用&#xff1a;正向代理、反向代理、负载均衡。常用命令和配置文件,很全_java冯坚持的博客-CSDN博客 前端必备的nginx知识点 - 掘金 前言&#xff1a; 最近在做一个后台项目 ngixn简介 nginx是一款轻量级&…

【Sql】sql语句练习随记

本文通过最经典的“学生-成绩-课程-教师”表来帮助练习sql语句。 STUDENT表 SNO 学号SNAME 姓名SSEX 性别SBIRTHDAY 生日CLASS 班级 SCORE表 SNO 学号CNO 课程编号DEGREE 分数 COURSE表 CNO 课程编号CNAME 课程名称TNO 教师编号 TEACHER表 TNO 教师编号TNAME 教师姓名TS…

(包教包会)最强分布式锁工具:Redisson

今天来聊聊分布式锁的最强实现&#xff1a;Redisson 从分布式锁到Redisson实现非常详细&#xff0c;适合慢慢咀嚼~ 一. Redisson概述 1.1 什么是Redisson&#xff1f; Redisson是一个在Redis的基础上实现的Java驻内存数据网格&#xff08;In-Memory Data Grid&#xff09;。…

CRMEB开源商城/标准版后台默认admin路径修改、后台目录地址修改

默认admin路径修改 v4.7 版本开始兼容可以修改admin路径&#xff0c;可以根据自己的要求更换后台访问地址&#xff1b; 默认路径不能携带特殊字符、全部都为小写字母 修改后台admin默认路径 标准版本文件位置根目录/config/app.php 开源版本文件位置根目录/crmeb/config/app.p…

新增套餐/redis/QuartZ

套餐其实就是检查组的集合&#xff0c;例如有一个套餐为“入职体检套餐”&#xff0c;这个体检套餐可以包括多个检查组&#xff1a;一般检查、血常规、尿常规、肝功三项等。所以在添加套餐时需要选择这个套餐包括的检查组。 套餐对应的实体类为Setmeal&#xff0c;对应的数据表…

深度学习(7)之图像抠图 Image Matting算法调研

目录 1.Trimap和Strokes 2. 相关数据集 3.论文算法调研 3.1 Deep Image Matting 3.2 Semantic Image Matting 3.3 Background Matting 3.4 Background Matting V2 3.5 Semantic Human Matting 3.6 HAttMatting 3.7 MMNet&#xff1a;Towards Real-Time Automatic Portrait Matt…

Nginx虚拟主机、优化和防盗链

一、Nginx虚拟主机 1、基于域名的nginx虚拟主机的操作步骤 1.1 为虚拟主机提供域名和IP的映射(也可以使用DNS正向解析) echo "192.168.2.66 www.xkq.com www.wy.com" >> /etc/hosts1.2 为虚拟主机准备网页文档 mkdir -p /var/www/html/ly/ mkdir -p /var/w…

经典文献阅读之--VGICP(体素化的ICP匹配)

0. 简介 之前我们在以前的文章中介绍了很多有关于点云匹配相关的知识&#xff0c;最近两年处理GICP这一大一统的ICP匹配方法以外&#xff0c;还有一个工作对体素化和ICP这两者打起了心思&#xff0c;《Voxelized GICP for Fast and Accurate 3D Point Cloud Registration》提出…

动车的颜色种类

01 大家平时见到最多的动车 可能就是白色的吧 和谐号动车组开行后 白就是动车的经典配色 白色为底 车腰一条蓝色飘带让其设计感十足 我们也可以把这种动车配色 称为和谐白 02 复兴号CR400AF型电力动车组 最高运营速度350公里每小时 因其车身采用银色底色 搭配侧窗…

大数据时代必备技能,从0开始学好数据可视化

当今社会&#xff0c;数据可视化已经成为了一项非常重要的技能。随着大数据时代的到来&#xff0c;越来越多的人开始关注数据可视化&#xff0c;并希望能够快速地掌握这项技能。那么&#xff0c;如何快速学习数据可视化呢&#xff1f;以下是AdBright数据分析师的一些建议。 什么…

CDN的发展史和未来

互联网是一种不断变化的机制&#xff0c;新形式的数据和内容正在不断创造。在它商业化后不久&#xff0c;必须解决尽快将大量数据推送给最终用户的问题&#xff0c;CDN因此应运而生。 CDN的根源产生于近20年前&#xff0c;并一直是内容交付的驱动力&#xff0c;自其创立以来&a…

企业电子招投标系统源码之了解电子招标投标全流程

随着各级政府部门的大力推进&#xff0c;以及国内互联网的建设&#xff0c;电子招投标已经逐渐成为国内主流的招标投标方式&#xff0c;但是依然有很多人对电子招投标的流程不够了解&#xff0c;在具体操作上存在困难。虽然各个交易平台的招标投标在线操作会略有不同&#xff0…

虚拟机里安装ubuntu-23.04-beta-desktop-amd64,开启SSH(换源、备份),配置中文以及中文输入法等

一、下载 官网 清华镜像站(推荐) 二、配置虚拟机 【自定义】 点击“下一步”&#xff0c;此处【默认】&#xff0c;再点击“下一步”。 点击“稍后安装操作系统”&#xff0c;再点击“下一步”。 点击“Linux(L)”&#xff0c;版本选择【Ubuntu 64 位】&#xff0c;再点击…

升级H2数据库2.x版本遇见的问题

目录 一、引言二、集成H2基础配置三、升级H2版本2.x遇到的问题报错1报错2 三、H2关键字 一、引言 之前在跑代码单元测试时&#xff0c;一直用的内存数据库H2代替实际的Mysql数据库&#xff0c;如此便省去了对Dao的大量mock代码&#xff0c;类似于在跑Junit单元测试时直接跑了集…

报错:crbug/1173575 non-js module files deprecated

文章目录 报错分析解决方法一&#xff1a;尽可能使用JS模块文件方法二&#xff1a;使用type"module"属性方法三&#xff1a;忽略警告 报错 分析 这个错误报告 (crbug/1173575) 指的是非 JavaScript 模块文件将不再被支持&#xff0c;并且已经弃用。这个问题是因为Ch…